Merge branch 'master' into master

This commit is contained in:
Bashar Astifan 2023-07-17 23:20:52 +04:00 committed by GitHub
commit a35c8425e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
520 changed files with 16053 additions and 8419 deletions

View file

@ -155,14 +155,8 @@ jobs:
extra: android
cc: clang
cxx: clang++
args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a OPENXR=1 OPENXR_PLATFORM_QUEST=1
id: android-vr-quest
- os: ubuntu-latest
extra: android
cc: clang
cxx: clang++
args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a OPENXR=1 OPENXR_PLATFORM_PICO=1
id: android-vr-pico
args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a OPENXR=1
id: android-vr
- os: ubuntu-latest
extra: android
cc: clang

2
.gitignore vendored
View file

@ -131,3 +131,5 @@ CMakeFiles
.cache/
build
libretro/obj/local
ppsspp_retroachievements.dat

3
.gitmodules vendored
View file

@ -44,3 +44,6 @@
[submodule "cpu_features"]
path = ext/cpu_features
url = https://github.com/google/cpu_features.git
[submodule "ext/rcheevos"]
path = ext/rcheevos
url = https://github.com/RetroAchievements/rcheevos.git

View file

@ -102,6 +102,13 @@ if(ANDROID OR WIN32 OR (UNIX AND NOT ARM_NO_VULKAN))
set(VULKAN ON)
endif()
# Default to bundled SDL2 on macOS, system SDL2 elsewhere.
if(APPLE AND NOT IOS)
set(DEFAULT_USE_SYSTEM_LIBSDL2 OFF)
else()
set(DEFAULT_USE_SYSTEM_LIBSDL2 ON)
endif()
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules)
if(NOT IOS)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/sdl)
@ -157,7 +164,7 @@ option(USE_ARMIPS "Build with armips support in API/debugger" ON)
option(USE_SYSTEM_SNAPPY "Dynamically link against system snappy" ${USE_SYSTEM_SNAPPY})
option(USE_SYSTEM_FFMPEG "Dynamically link against system FFMPEG" ${USE_SYSTEM_FFMPEG})
option(USE_SYSTEM_LIBZIP "Dynamically link against system libzip" ${USE_SYSTEM_LIBZIP})
option(USE_SYSTEM_LIBSDL2 "Dynamically link against system SDL2" ON) # Ignored on Mac
option(USE_SYSTEM_LIBSDL2 "Dynamically link against system SDL2" ${DEFAULT_USE_SYSTEM_LIBSDL2})
option(USE_SYSTEM_LIBPNG "Dynamically link against system libpng" ON)
option(USE_SYSTEM_ZSTD "Dynamically link against system zstd" ${USE_SYSTEM_ZSTD})
option(USE_SYSTEM_MINIUPNPC "Dynamically link against system miniUPnPc" ${USE_SYSTEM_MINIUPNPC})
@ -167,6 +174,8 @@ option(USE_UBSAN "Use undefined behaviour sanitizer" OFF)
if(UNIX AND NOT (APPLE OR ANDROID) AND VULKAN)
if(USING_X11_VULKAN)
message("Using X11 for Vulkan")
find_package(X11)
include_directories(${X11_Xlib_INCLUDE_PATH})
add_definitions(-DVK_USE_PLATFORM_XLIB_KHR)
else()
message("NOT using X11 for Vulkan")
@ -242,9 +251,13 @@ if(NOT LIBRETRO AND NOT IOS AND NOT MACOSX)
endif()
if(MACOSX AND NOT IOS)
if(USE_SYSTEM_LIBSDL2)
find_package(SDL2)
else()
find_library(SDL2Fwk SDL2 REQUIRED PATHS SDL/macOS)
message(STATUS "found SDL2Fwk=${SDL2Fwk}")
add_definitions(-DHAVE_SYSCTLBYNAME)
endif()
endif()
include(FindThreads)
@ -393,7 +406,7 @@ if(NOT MSVC)
add_definitions(-Wno-psabi)
endif()
add_definitions(-D_XOPEN_SOURCE=700)
add_definitions(-D_XOPEN_SOURCE_EXTENDED -D__BSD_VISIBLE=1)
add_definitions(-D_XOPEN_SOURCE_EXTENDED -D__BSD_VISIBLE=1 -D_BSD_SOURCE)
add_definitions(-D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64)
elseif(ANDROID)
add_definitions(-fsigned-char)
@ -406,6 +419,7 @@ else()
endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -D_NDEBUG")
set(CMAKE_EXE_LINKER_FLAGS /NODEFAULTLIB:"libcmt.lib")
endif()
if(WIN32)
@ -555,6 +569,7 @@ add_library(Common STATIC
Common/Data/Collections/FixedSizeQueue.h
Common/Data/Collections/Hashmaps.h
Common/Data/Collections/TinySet.h
Common/Data/Collections/FastVec.h
Common/Data/Collections/ThreadSafeList.h
Common/Data/Color/RGBAUtil.cpp
Common/Data/Color/RGBAUtil.h
@ -603,6 +618,8 @@ add_library(Common STATIC
Common/File/VFS/DirectoryReader.h
Common/File/AndroidStorage.h
Common/File/AndroidStorage.cpp
Common/File/AndroidContentURI.h
Common/File/AndroidContentURI.cpp
Common/File/DiskFree.h
Common/File/DiskFree.cpp
Common/File/Path.h
@ -617,6 +634,8 @@ add_library(Common STATIC
Common/File/FileDescriptor.h
Common/GPU/DataFormat.h
Common/GPU/MiscTypes.h
Common/GPU/GPUBackendCommon.cpp
Common/GPU/GPUBackendCommon.h
Common/GPU/thin3d.cpp
Common/GPU/thin3d.h
Common/GPU/thin3d_create.h
@ -638,6 +657,8 @@ add_library(Common STATIC
Common/GPU/OpenGL/GLFrameData.cpp
Common/GPU/OpenGL/GLFrameData.h
Common/GPU/OpenGL/thin3d_gl.cpp
Common/GPU/OpenGL/GLMemory.cpp
Common/GPU/OpenGL/GLMemory.h
Common/GPU/OpenGL/GLRenderManager.cpp
Common/GPU/OpenGL/GLRenderManager.h
Common/GPU/OpenGL/GLQueueRunner.cpp
@ -723,6 +744,8 @@ add_library(Common STATIC
Common/System/NativeApp.h
Common/System/Request.cpp
Common/System/Request.h
Common/System/OSD.cpp
Common/System/OSD.h
Common/Thread/Channel.h
Common/Thread/ParallelLoop.cpp
Common/Thread/ParallelLoop.h
@ -741,6 +764,8 @@ add_library(Common STATIC
Common/UI/UI.h
Common/UI/Context.cpp
Common/UI/Context.h
Common/UI/IconCache.cpp
Common/UI/IconCache.h
Common/UI/UIScreen.cpp
Common/UI/UIScreen.h
Common/UI/Tween.cpp
@ -778,8 +803,10 @@ add_library(Common STATIC
Common/MemArenaDarwin.cpp
Common/MemArenaPosix.cpp
Common/MemArenaWin32.cpp
Common/MemArenaHorizon.cpp
Common/MemArena.h
Common/MemoryUtil.cpp
Common/MemoryUtilHorizon.cpp
Common/MemoryUtil.h
Common/OSVersion.cpp
Common/OSVersion.h
@ -919,6 +946,7 @@ endif()
find_package(LIBZIP)
if(LIBZIP_FOUND AND USE_SYSTEM_LIBZIP)
include_directories(${LIBZIP_INCLUDE_DIRS})
add_definitions(-DSHARED_LIBZIP)
else()
add_library(libzip STATIC
@ -1297,7 +1325,12 @@ else()
set_source_files_properties(SDL/PPSSPPAboutViewController.m PROPERTIES COMPILE_FLAGS -fobjc-arc)
set_source_files_properties(Common/Battery/AppleBatteryClient.m PROPERTIES COMPILE_FLAGS -fobjc-arc)
set(nativeExtraLibs ${nativeExtraLibs} ${COCOA_LIBRARY} ${QUARTZ_CORE_LIBRARY} ${IOKIT_LIBRARY})
if(USE_SYSTEM_LIBSDL2)
set(nativeExtraLibs ${nativeExtraLibs} SDL2::SDL2)
else()
set(nativeExtraLibs ${nativeExtraLibs} ${SDL2Fwk})
endif()
elseif(USING_EGL)
set(nativeExtraLibs ${nativeExtraLibs} pthread SDL2::SDL2)
else()
@ -1345,6 +1378,8 @@ list(APPEND NativeAppSource
UI/MiscScreens.cpp
UI/PauseScreen.h
UI/PauseScreen.cpp
UI/TabbedDialogScreen.h
UI/TabbedDialogScreen.cpp
UI/GameScreen.h
UI/GameScreen.cpp
UI/GameSettingsScreen.h
@ -1385,6 +1420,8 @@ list(APPEND NativeAppSource
UI/CustomButtonMappingScreen.cpp
UI/Theme.h
UI/Theme.cpp
UI/RetroAchievementScreens.cpp
UI/RetroAchievementScreens.h
)
if(ANDROID)
@ -1786,6 +1823,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/KeyMap.h
Core/KeyMapDefaults.cpp
Core/KeyMapDefaults.h
Core/RetroAchievements.h
Core/RetroAchievements.cpp
Core/ThreadEventQueue.h
Core/TiltEventProcessor.h
Core/TiltEventProcessor.cpp
@ -1812,6 +1851,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/Debugger/WebSocket/GameBroadcaster.h
Core/Debugger/WebSocket/GameSubscriber.cpp
Core/Debugger/WebSocket/GameSubscriber.h
Core/Debugger/WebSocket/ClientConfigSubscriber.cpp
Core/Debugger/WebSocket/ClientConfigSubscriber.h
Core/Debugger/WebSocket/GPUBufferSubscriber.cpp
Core/Debugger/WebSocket/GPUBufferSubscriber.h
Core/Debugger/WebSocket/GPURecordSubscriber.cpp
@ -2179,7 +2220,7 @@ else()
include_directories(ext/zstd/lib)
endif()
target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash ${GlslangLibs}
target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash rcheevos ${GlslangLibs}
${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS})
target_compile_features(${CoreLibName} PUBLIC cxx_std_17)
@ -2328,7 +2369,6 @@ set(WindowsFiles
Windows/Debugger/Debugger_MemoryDlg.h
Windows/Debugger/Debugger_Lists.cpp
Windows/Debugger/Debugger_Lists.h
Windows/Debugger/Debugger_SymbolMap.h
Windows/Debugger/Debugger_VFPUDlg.cpp
Windows/Debugger/Debugger_VFPUDlg.h
Windows/Debugger/WatchItemWindow.cpp
@ -2376,6 +2416,13 @@ set(WindowsFiles
Windows/W32Util/ShellUtil.h
Windows/W32Util/TabControl.cpp
Windows/W32Util/TabControl.h
Windows/W32Util/IatHook.h
Windows/W32Util/ContextMenu.h
Windows/W32Util/ContextMenu.cpp
Windows/W32Util/DarkMode.h
Windows/W32Util/DarkMode.cpp
Windows/W32Util/UAHMenuBar.h
Windows/W32Util/UAHMenuBar.cpp
Windows/WindowsHost.cpp
Windows/WindowsHost.h
Windows/MainWindow.cpp
@ -2399,7 +2446,7 @@ set(WindowsFiles
list(APPEND LinkCommon ${CoreLibName} ${CMAKE_THREAD_LIBS_INIT})
if(WIN32)
list(APPEND LinkCommon kernel32 user32 gdi32 shell32 comctl32 dsound xinput d3d9 winmm dinput8 ole32 winspool ksuser mf mfplat mfreadwrite mfuuid shlwapi)
list(APPEND LinkCommon kernel32 user32 gdi32 shell32 comctl32 dsound xinput d3d9 winmm dinput8 ole32 winspool ksuser mf uxtheme mfplat mfreadwrite mfuuid shlwapi)
#setup_target_project(${TargetBin} Windows)
list(APPEND NativeAppSource ${WindowsFiles})
endif()
@ -2558,7 +2605,7 @@ if(TargetBin)
file(INSTALL "${CMAKE_SOURCE_DIR}/ext/vulkan/macOS/Frameworks/libMoltenVK.dylib" DESTINATION "${CMAKE_BINARY_DIR}/${TargetBin}.app/Contents/Frameworks/")
if(USING_QT_UI)
add_custom_command(TARGET ${TargetBin} POST_BUILD COMMAND /bin/bash "${CMAKE_SOURCE_DIR}/Qt/macbundle.sh" "${CMAKE_BINARY_DIR}/PPSSPPQt.app")
else()
elseif(NOT USE_SYSTEM_LIBSDL2)
add_custom_command(TARGET ${TargetBin} POST_BUILD COMMAND /bin/bash "${CMAKE_SOURCE_DIR}/SDL/macbundle.sh" "${CMAKE_BINARY_DIR}/${TargetBin}.app" "${TargetBin}")
endif()
endif()

View file

@ -315,6 +315,14 @@ const u8* ARM64XEmitter::AlignCodePage()
return m_code;
}
const u8 *ARM64XEmitter::NopAlignCode16() {
int bytes = ((-(intptr_t)m_code) & 15);
for (int i = 0; i < bytes / 4; i++) {
Write32(0xD503201F); // official nop instruction
}
return m_code;
}
void ARM64XEmitter::FlushIcache()
{
FlushIcacheSection(m_lastCacheFlushEnd, m_code);

View file

@ -94,7 +94,7 @@ enum ARM64Reg
// R19-R28. R29 (FP), R30 (LR) are always saved and FP updated appropriately.
const u32 ALL_CALLEE_SAVED = 0x1FF80000;
const u32 ALL_CALLEE_SAVED_FP = 0x0000FF00; // d8-d15
const u32 ALL_CALLEE_SAVED_FP = 0x0000FF00; // q8-q15
inline bool Is64Bit(ARM64Reg reg) { return (reg & 0x20) != 0; }
inline bool IsSingle(ARM64Reg reg) { return (reg & 0xC0) == 0x40; }
@ -401,6 +401,7 @@ public:
void ReserveCodeSpace(u32 bytes);
const u8* AlignCode16();
const u8* AlignCodePage();
const u8 *NopAlignCode16();
void FlushIcache();
void FlushIcacheSection(const u8* start, const u8* end);
u8* GetWritableCodePtr();

View file

@ -32,14 +32,15 @@
#if defined(CPU_FEATURES_OS_LINUX)
#define USE_CPU_FEATURES 1
#endif
#elif PPSSPP_ARCH(ARM64) && defined(__aarch64__)
#elif PPSSPP_ARCH(ARM64)
#include "ext/cpu_features/include/cpuinfo_aarch64.h"
#if defined(CPU_FEATURES_OS_LINUX) || defined(CPU_FEATURES_OS_ANDROID)
#if defined(CPU_FEATURES_OS_LINUX) || defined(CPU_FEATURES_OS_ANDROID) || defined(CPU_FEATURES_OS_WINDOWS)
#define USE_CPU_FEATURES 1
#endif
#endif
#include <cstring>
#include <ctype.h>
#include "Common/CommonTypes.h"
@ -54,7 +55,7 @@
std::string GetCPUBrandString();
#else
// No CPUID on ARM, so we'll have to read the registry
#include <windows.h>
#include "Common/CommonWindows.h"
std::string GetCPUBrandString() {
std::string cpu_string;

View file

@ -613,6 +613,14 @@ const u8 *ARMXEmitter::AlignCode16()
return code;
}
const u8 *ARMXEmitter::NopAlignCode16() {
int bytes = ((-(intptr_t)code) & 15);
for (int i = 0; i < bytes / 4; i++) {
Write32(0xE320F000); // one of many possible nops
}
return code;
}
const u8 *ARMXEmitter::AlignCodePage()
{
ReserveCodeSpace((-(intptr_t)code) & 4095);

View file

@ -446,6 +446,8 @@ public:
void ReserveCodeSpace(u32 bytes);
const u8 *AlignCode16();
const u8 *AlignCodePage();
const u8 *NopAlignCode16();
void FlushIcache();
void FlushIcacheSection(u8 *start, u8 *end);
u8 *GetWritableCodePtr();

View file

@ -10,6 +10,11 @@
#include "Common/Log.h"
#include "Common/MemoryUtil.h"
#if PPSSPP_PLATFORM(SWITCH)
#include <cstdio>
#include <switch.h>
#endif // PPSSPP_PLATFORM(SWITCH)
// Everything that needs to generate code should inherit from this.
// You get memory management for free, plus, you can use all emitter functions without
// having to prefix them with gen-> or something similar.
@ -65,9 +70,20 @@ public:
// Call this before you generate any code.
void AllocCodeSpace(int size) {
region_size = size;
#if PPSSPP_PLATFORM(SWITCH)
Result rc = jitCreate(&jitController, size);
if(R_FAILED(rc)) {
printf("Failed to create Jitbuffer of size 0x%x err: 0x%x\n", size, rc);
}
printf("[NXJIT]: Initialized RX: %p RW: %p\n", jitController.rx_addr, jitController.rw_addr);
region = (u8 *)jitController.rx_addr;
writableRegion = (u8 *)jitController.rw_addr;
#else // PPSSPP_PLATFORM(SWITCH)
// The protection will be set to RW if PlatformIsWXExclusive.
region = (u8 *)AllocateExecutableMemory(region_size);
writableRegion = region;
#endif // !PPSSPP_PLATFORM(SWITCH)
T::SetCodePointer(region, writableRegion);
}
@ -135,8 +151,13 @@ public:
// Call this when shutting down. Don't rely on the destructor, even though it'll do the job.
void FreeCodeSpace() {
#if !PPSSPP_PLATFORM(SWITCH)
ProtectMemoryPages(region, region_size, MEM_PROT_READ | MEM_PROT_WRITE);
FreeExecutableMemory(region, region_size);
#else // !PPSSPP_PLATFORM(SWITCH)
jitClose(&jitController);
printf("[NXJIT]: Jit closed\n");
#endif // PPSSPP_PLATFORM(SWITCH)
region = nullptr;
writableRegion = nullptr;
region_size = 0;
@ -176,5 +197,7 @@ private:
const uint8_t *writeStart_ = nullptr;
uint8_t *writableRegion = nullptr;
size_t writeEstimated_ = 0;
#if PPSSPP_PLATFORM(SWITCH)
Jit jitController;
#endif // PPSSPP_PLATFORM(SWITCH)
};

View file

@ -434,6 +434,7 @@
<ClInclude Include="Data\Text\Parsers.h" />
<ClInclude Include="Data\Text\WrapText.h" />
<ClInclude Include="FakeEmitter.h" />
<ClInclude Include="File\AndroidContentURI.h" />
<ClInclude Include="File\AndroidStorage.h" />
<ClInclude Include="File\DirListing.h" />
<ClInclude Include="File\DiskFree.h" />
@ -449,11 +450,13 @@
<ClInclude Include="GPU\D3D9\D3D9ShaderCompiler.h" />
<ClInclude Include="GPU\D3D9\D3D9StateCache.h" />
<ClInclude Include="GPU\DataFormat.h" />
<ClInclude Include="GPU\GPUBackendCommon.h" />
<ClInclude Include="GPU\MiscTypes.h" />
<ClInclude Include="GPU\OpenGL\DataFormatGL.h" />
<ClInclude Include="GPU\OpenGL\gl3stub.h" />
<ClInclude Include="GPU\OpenGL\GLFeatures.h" />
<ClInclude Include="GPU\OpenGL\GLFrameData.h" />
<ClInclude Include="GPU\OpenGL\GLMemory.h" />
<ClInclude Include="GPU\OpenGL\GLQueueRunner.h" />
<ClInclude Include="GPU\OpenGL\GLRenderManager.h" />
<ClInclude Include="GPU\OpenGL\GLSLProgram.h" />
@ -558,6 +561,7 @@
<ClInclude Include="Swap.h" />
<ClInclude Include="SysError.h" />
<ClInclude Include="System\Display.h" />
<ClInclude Include="System\OSD.h" />
<ClInclude Include="System\Request.h" />
<ClInclude Include="System\NativeApp.h" />
<ClInclude Include="System\System.h" />
@ -573,6 +577,7 @@
<ClInclude Include="TimeUtil.h" />
<ClInclude Include="UI\AsyncImageFileView.h" />
<ClInclude Include="UI\Context.h" />
<ClInclude Include="UI\IconCache.h" />
<ClInclude Include="UI\PopupScreens.h" />
<ClInclude Include="UI\Root.h" />
<ClInclude Include="UI\Screen.h" />
@ -857,6 +862,7 @@
</ClCompile>
<ClCompile Include="ArmEmitter.cpp" />
<ClCompile Include="Buffer.cpp" />
<ClCompile Include="Data\Collections\FastVec.h" />
<ClCompile Include="Data\Color\RGBAUtil.cpp" />
<ClCompile Include="Data\Convert\SmallDataConvert.cpp" />
<ClCompile Include="Data\Encoding\Base64.cpp" />
@ -874,6 +880,7 @@
<ClCompile Include="Data\Text\I18n.cpp" />
<ClCompile Include="Data\Text\Parsers.cpp" />
<ClCompile Include="Data\Text\WrapText.cpp" />
<ClCompile Include="File\AndroidContentURI.cpp" />
<ClCompile Include="File\AndroidStorage.cpp" />
<ClCompile Include="File\DirListing.cpp" />
<ClCompile Include="File\DiskFree.cpp" />
@ -890,10 +897,12 @@
<ClCompile Include="GPU\D3D9\D3D9ShaderCompiler.cpp" />
<ClCompile Include="GPU\D3D9\D3D9StateCache.cpp" />
<ClCompile Include="GPU\D3D9\thin3d_d3d9.cpp" />
<ClCompile Include="GPU\GPUBackendCommon.cpp" />
<ClCompile Include="GPU\OpenGL\DataFormatGL.cpp" />
<ClCompile Include="GPU\OpenGL\gl3stub.c" />
<ClCompile Include="GPU\OpenGL\GLFeatures.cpp" />
<ClCompile Include="GPU\OpenGL\GLFrameData.cpp" />
<ClCompile Include="GPU\OpenGL\GLMemory.cpp" />
<ClCompile Include="GPU\OpenGL\GLQueueRunner.cpp" />
<ClCompile Include="GPU\OpenGL\GLRenderManager.cpp" />
<ClCompile Include="GPU\OpenGL\GLSLProgram.cpp" />
@ -1014,6 +1023,7 @@
<ClCompile Include="OSVersion.cpp" />
<ClCompile Include="StringUtils.cpp" />
<ClCompile Include="System\Display.cpp" />
<ClCompile Include="System\OSD.cpp" />
<ClCompile Include="System\Request.cpp" />
<ClCompile Include="Thread\ParallelLoop.cpp" />
<ClCompile Include="Thread\ThreadManager.cpp" />
@ -1022,6 +1032,7 @@
<ClCompile Include="TimeUtil.cpp" />
<ClCompile Include="UI\AsyncImageFileView.cpp" />
<ClCompile Include="UI\Context.cpp" />
<ClCompile Include="UI\IconCache.cpp" />
<ClCompile Include="UI\PopupScreens.cpp" />
<ClCompile Include="UI\Root.cpp" />
<ClCompile Include="UI\Screen.cpp" />

View file

@ -449,9 +449,6 @@
<ClInclude Include="Render\ManagedTexture.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="GPU\MiscTypes.h">
<Filter>GPU</Filter>
</ClInclude>
<ClInclude Include="GPU\Vulkan\VulkanFramebuffer.h">
<Filter>GPU\Vulkan</Filter>
</ClInclude>
@ -497,6 +494,24 @@
<ClInclude Include="System\Request.h">
<Filter>System</Filter>
</ClInclude>
<ClInclude Include="File\AndroidContentURI.h">
<Filter>File</Filter>
</ClInclude>
<ClInclude Include="GPU\OpenGL\GLMemory.h">
<Filter>GPU\OpenGL</Filter>
</ClInclude>
<ClInclude Include="GPU\GPUBackendCommon.h">
<Filter>GPU</Filter>
</ClInclude>
<ClInclude Include="GPU\MiscTypes.h">
<Filter>GPU</Filter>
</ClInclude>
<ClInclude Include="UI\IconCache.h">
<Filter>UI</Filter>
</ClInclude>
<ClInclude Include="System\OSD.h">
<Filter>System</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ABI.cpp" />
@ -926,6 +941,24 @@
<ClCompile Include="System\Request.cpp">
<Filter>System</Filter>
</ClCompile>
<ClCompile Include="File\AndroidContentURI.cpp">
<Filter>File</Filter>
</ClCompile>
<ClCompile Include="GPU\OpenGL\GLMemory.cpp">
<Filter>GPU\OpenGL</Filter>
</ClCompile>
<ClCompile Include="Data\Collections\FastVec.h">
<Filter>Data\Collections</Filter>
</ClCompile>
<ClCompile Include="GPU\GPUBackendCommon.cpp">
<Filter>GPU</Filter>
</ClCompile>
<ClCompile Include="UI\IconCache.cpp">
<Filter>UI</Filter>
</ClCompile>
<ClCompile Include="System\OSD.cpp">
<Filter>System</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="Crypto">

View file

@ -32,6 +32,9 @@
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#define Crash() {asm ("int $3");}
#elif PPSSPP_PLATFORM(SWITCH)
// TODO: Implement Crash() for Switch, lets not use breakpoint for the time being
#define Crash() {*((volatile u32 *)0x0) = 0xDEADC0DE;}
#elif PPSSPP_ARCH(ARM)
#define Crash() {asm ("bkpt #0");}
#elif PPSSPP_ARCH(ARM64)

View file

@ -36,6 +36,39 @@ typedef signed __int64 s64;
#else
#ifdef __SWITCH__
// Some HID conflicts
#define KEY_UP PKEY_UP
#define KEY_DOWN PKEY_DOWN
// Other conflicts
#define Event _Event
#define Framebuffer _Framebuffer
#define Waitable _Waitable
#define ThreadContext _ThreadContext
#include <switch.h>
// Cleanup
#undef KEY_UP
#undef KEY_DOWN
#undef Event
#undef Framebuffer
#undef Waitable
#undef ThreadContext
// Conflicting types with libnx
#ifndef _u64
#define u64 _u64
#endif // _u64
#ifndef s64
#define s64 _s64
#endif // _s64
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
#endif // __SWITCH__
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;

View file

@ -62,7 +62,7 @@
/*
* MD5 context setup
*/
void md5_starts( md5_context *ctx )
void ppsspp_md5_starts( md5_context *ctx )
{
ctx->total[0] = 0;
ctx->total[1] = 0;
@ -73,7 +73,7 @@ void md5_starts( md5_context *ctx )
ctx->state[3] = 0x10325476;
}
static void md5_process( md5_context *ctx, unsigned char data[64] )
static void ppsspp_md5_process( md5_context *ctx, unsigned char data[64] )
{
unsigned long X[16], A, B, C, D;
@ -199,7 +199,7 @@ static void md5_process( md5_context *ctx, unsigned char data[64] )
/*
* MD5 process buffer
*/
void md5_update( md5_context *ctx, unsigned char *input, int ilen )
void ppsspp_md5_update( md5_context *ctx, unsigned char *input, int ilen )
{
int fill;
unsigned long left;
@ -220,7 +220,7 @@ void md5_update( md5_context *ctx, unsigned char *input, int ilen )
{
memcpy( (void *) (ctx->buffer + left),
(void *) input, fill );
md5_process( ctx, ctx->buffer );
ppsspp_md5_process( ctx, ctx->buffer );
input += fill;
ilen -= fill;
left = 0;
@ -228,7 +228,7 @@ void md5_update( md5_context *ctx, unsigned char *input, int ilen )
while( ilen >= 64 )
{
md5_process( ctx, input );
ppsspp_md5_process( ctx, input );
input += 64;
ilen -= 64;
}
@ -251,7 +251,7 @@ static const unsigned char md5_padding[64] =
/*
* MD5 final digest
*/
void md5_finish( md5_context *ctx, unsigned char output[16] )
void ppsspp_md5_finish( md5_context *ctx, unsigned char output[16] )
{
unsigned long last, padn;
unsigned long high, low;
@ -267,8 +267,8 @@ void md5_finish( md5_context *ctx, unsigned char output[16] )
last = ctx->total[0] & 0x3F;
padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last );
md5_update( ctx, (unsigned char *) md5_padding, padn );
md5_update( ctx, msglen, 8 );
ppsspp_md5_update( ctx, (unsigned char *) md5_padding, padn );
ppsspp_md5_update( ctx, msglen, 8 );
PUT_ULONG_LE( ctx->state[0], output, 0 );
PUT_ULONG_LE( ctx->state[1], output, 4 );
@ -279,13 +279,13 @@ void md5_finish( md5_context *ctx, unsigned char output[16] )
/*
* output = MD5( input buffer )
*/
void md5( unsigned char *input, int ilen, unsigned char output[16] )
void ppsspp_md5( unsigned char *input, int ilen, unsigned char output[16] )
{
md5_context ctx;
md5_starts( &ctx );
md5_update( &ctx, input, ilen );
md5_finish( &ctx, output );
ppsspp_md5_starts( &ctx );
ppsspp_md5_update( &ctx, input, ilen );
ppsspp_md5_finish( &ctx, output );
memset( &ctx, 0, sizeof( md5_context ) );
}
@ -293,14 +293,14 @@ void md5( unsigned char *input, int ilen, unsigned char output[16] )
/*
* MD5 HMAC context setup
*/
void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen )
void ppsspp_md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen )
{
int i;
unsigned char sum[16];
if( keylen > 64 )
{
md5( key, keylen, sum );
ppsspp_md5( key, keylen, sum );
keylen = 16;
key = sum;
}
@ -314,8 +314,8 @@ void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen )
ctx->opad[i] = (unsigned char)( ctx->opad[i] ^ key[i] );
}
md5_starts( ctx );
md5_update( ctx, ctx->ipad, 64 );
ppsspp_md5_starts( ctx );
ppsspp_md5_update( ctx, ctx->ipad, 64 );
memset( sum, 0, sizeof( sum ) );
}
@ -323,23 +323,23 @@ void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen )
/*
* MD5 HMAC process buffer
*/
void md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen )
void ppsspp_md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen )
{
md5_update( ctx, input, ilen );
ppsspp_md5_update( ctx, input, ilen );
}
/*
* MD5 HMAC final digest
*/
void md5_hmac_finish( md5_context *ctx, unsigned char output[16] )
void ppsspp_md5_hmac_finish( md5_context *ctx, unsigned char output[16] )
{
unsigned char tmpbuf[16];
md5_finish( ctx, tmpbuf );
md5_starts( ctx );
md5_update( ctx, ctx->opad, 64 );
md5_update( ctx, tmpbuf, 16 );
md5_finish( ctx, output );
ppsspp_md5_finish( ctx, tmpbuf );
ppsspp_md5_starts( ctx );
ppsspp_md5_update( ctx, ctx->opad, 64 );
ppsspp_md5_update( ctx, tmpbuf, 16 );
ppsspp_md5_finish( ctx, output );
memset( tmpbuf, 0, sizeof( tmpbuf ) );
}
@ -347,14 +347,14 @@ void md5_hmac_finish( md5_context *ctx, unsigned char output[16] )
/*
* output = HMAC-MD5( hmac key, input buffer )
*/
void md5_hmac( unsigned char *key, int keylen, unsigned char *input, int ilen,
void ppsspp_md5_hmac( unsigned char *key, int keylen, unsigned char *input, int ilen,
unsigned char output[16] )
{
md5_context ctx;
md5_hmac_starts( &ctx, key, keylen );
md5_hmac_update( &ctx, input, ilen );
md5_hmac_finish( &ctx, output );
ppsspp_md5_hmac_starts( &ctx, key, keylen );
ppsspp_md5_hmac_update( &ctx, input, ilen );
ppsspp_md5_hmac_finish( &ctx, output );
memset( &ctx, 0, sizeof( md5_context ) );
}
@ -464,7 +464,7 @@ static const unsigned char md5_hmac_test_sum[7][16] =
/*
* Checkup routine
*/
int md5_self_test( int verbose )
int ppsspp_md5_self_test( int verbose )
{
int i, buflen;
unsigned char buf[1024];

View file

@ -46,7 +46,7 @@ extern "C" {
*
* \param ctx context to be initialized
*/
void md5_starts( md5_context *ctx );
void ppsspp_md5_starts( md5_context *ctx );
/**
* \brief MD5 process buffer
@ -55,7 +55,7 @@ void md5_starts( md5_context *ctx );
* \param input buffer holding the data
* \param ilen length of the input data
*/
void md5_update( md5_context *ctx, unsigned char *input, int ilen );
void ppsspp_md5_update( md5_context *ctx, unsigned char *input, int ilen );
/**
* \brief MD5 final digest
@ -63,7 +63,7 @@ void md5_update( md5_context *ctx, unsigned char *input, int ilen );
* \param ctx MD5 context
* \param output MD5 checksum result
*/
void md5_finish( md5_context *ctx, unsigned char output[16] );
void ppsspp_md5_finish( md5_context *ctx, unsigned char output[16] );
/**
* \brief Output = MD5( input buffer )
@ -72,7 +72,7 @@ void md5_finish( md5_context *ctx, unsigned char output[16] );
* \param ilen length of the input data
* \param output MD5 checksum result
*/
void md5( unsigned char *input, int ilen, unsigned char output[16] );
void ppsspp_md5( unsigned char *input, int ilen, unsigned char output[16] );
/**
* \brief Output = MD5( file contents )
@ -83,7 +83,7 @@ void md5( unsigned char *input, int ilen, unsigned char output[16] );
* \return 0 if successful, 1 if fopen failed,
* or 2 if fread failed
*/
int md5_file( char *path, unsigned char output[16] );
int ppsspp_md5_file( char *path, unsigned char output[16] );
/**
* \brief MD5 HMAC context setup
@ -92,7 +92,7 @@ int md5_file( char *path, unsigned char output[16] );
* \param key HMAC secret key
* \param keylen length of the HMAC key
*/
void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen );
void ppsspp_md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen );
/**
* \brief MD5 HMAC process buffer
@ -101,7 +101,7 @@ void md5_hmac_starts( md5_context *ctx, unsigned char *key, int keylen );
* \param input buffer holding the data
* \param ilen length of the input data
*/
void md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen );
void ppsspp_md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen );
/**
* \brief MD5 HMAC final digest
@ -109,7 +109,7 @@ void md5_hmac_update( md5_context *ctx, unsigned char *input, int ilen );
* \param ctx HMAC context
* \param output MD5 HMAC checksum result
*/
void md5_hmac_finish( md5_context *ctx, unsigned char output[16] );
void ppsspp_md5_hmac_finish( md5_context *ctx, unsigned char output[16] );
/**
* \brief Output = HMAC-MD5( hmac key, input buffer )
@ -120,7 +120,7 @@ void md5_hmac_finish( md5_context *ctx, unsigned char output[16] );
* \param ilen length of the input data
* \param output HMAC-MD5 result
*/
void md5_hmac( unsigned char *key, int keylen,
void ppsspp_md5_hmac( unsigned char *key, int keylen,
unsigned char *input, int ilen,
unsigned char output[16] );
@ -129,7 +129,7 @@ void md5_hmac( unsigned char *key, int keylen,
*
* \return 0 if successful, or 1 if the test failed
*/
int md5_self_test( int verbose );
int ppsspp_md5_self_test( int verbose );
#ifdef __cplusplus
}

View file

@ -0,0 +1,157 @@
#pragma once
// Yet another replacement for std::vector, this time for use in graphics queues.
// Its major difference is that you can append uninitialized structures and initialize them after.
// This is not allows by std::vector but is very useful for our sometimes oversized unions.
// Also, copies during resize are done by memcpy, not by any move constructor or similar.
#include <cstdlib>
#include <cstring>
#ifdef _DEBUG
#include "Common/Log.h"
#endif
template<class T>
class FastVec {
public:
FastVec() {}
FastVec(size_t initialCapacity) {
capacity_ = initialCapacity;
data_ = (T *)malloc(initialCapacity * sizeof(T));
}
~FastVec() { if (data_) free(data_); }
T &push_uninitialized() {
if (size_ < capacity_) {
size_++;
return data_[size_ - 1];
} else {
ExtendByOne();
return data_[size_ - 1];
}
}
void push_back(const T &t) {
T &dest = push_uninitialized();
dest = t;
}
// Move constructor
FastVec(FastVec &&other) {
data_ = other.data_;
size_ = other.size_;
capacity_ = other.capacity_;
other.data_ = nullptr;
other.size_ = 0;
other.capacity_ = 0;
}
FastVec &operator=(FastVec &&other) {
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
capacity_ = other.capacity_;
other.data_ = nullptr;
other.size_ = 0;
other.capacity_ = 0;
}
return *this;
}
// No copy constructor.
FastVec(const FastVec &other) = delete;
FastVec &operator=(const FastVec &other) = delete;
size_t size() const { return size_; }
size_t capacity() const { return capacity_; }
void clear() { size_ = 0; }
bool empty() const { return size_ == 0; }
T *begin() { return data_; }
T *end() { return data_ + size_; }
const T *begin() const { return data_; }
const T *end() const { return data_ + size_; }
// Out of bounds (past size() - 1) is undefined behavior.
T &operator[] (const size_t index) { return data_[index]; }
const T &operator[] (const size_t index) const { return data_[index]; }
T &at(const size_t index) { return data_[index]; }
const T &at(const size_t index) const { return data_[index]; }
// These two are invalid if empty().
const T &back() const { return (*this)[size() - 1]; }
const T &front() const { return (*this)[0]; }
// Limited functionality for inserts and similar, add as needed.
T &insert(T *iter) {
int pos = iter - data_;
ExtendByOne();
if (pos + 1 < size_) {
memmove(data_ + pos + 1, data_ + pos, (size_ - pos) * sizeof(T));
}
return data_[pos];
}
void insert(T *destIter, const T *beginIter, const T *endIter) {
int pos = destIter - data_;
if (beginIter == endIter)
return;
size_t newItems = endIter - beginIter;
IncreaseCapacityTo(size_ + newItems);
memmove(data_ + pos + newItems, data_ + pos, (size_ - pos) * sizeof(T));
memcpy(data_ + pos, beginIter, newItems * sizeof(T));
size_ += newItems;
}
void resize(size_t size) {
if (size < size_) {
size_ = size;
} else {
// TODO
}
}
void reserve(size_t newCapacity) {
IncreaseCapacityTo(newCapacity);
}
void LockCapacity() {
#ifdef _DEBUG
capacityLocked_ = true;
#endif
}
private:
void IncreaseCapacityTo(size_t newCapacity) {
#ifdef _DEBUG
_dbg_assert_(!capacityLocked_);
#endif
if (newCapacity <= capacity_)
return;
T *oldData = data_;
data_ = (T *)malloc(sizeof(T) * newCapacity);
if (capacity_ != 0) {
memcpy(data_, oldData, sizeof(T) * size_);
free(oldData);
}
capacity_ = newCapacity;
}
void ExtendByOne() {
size_t newCapacity = capacity_ * 2;
if (newCapacity < 16) {
newCapacity = 16;
}
IncreaseCapacityTo(newCapacity);
size_++;
}
size_t size_ = 0;
size_t capacity_ = 0;
T *data_ = nullptr;
#ifdef _DEBUG
bool capacityLocked_ = false;
#endif
};

View file

@ -56,7 +56,7 @@ public:
return NullValue;
}
// Returns false if we already had the key! Which is a bit different.
// Asserts if we already had the key!
bool Insert(const Key &key, Value value) {
// Check load factor, resize if necessary. We never shrink.
if (count_ > capacity_ / 2) {

View file

@ -187,7 +187,7 @@ struct FixedTinyVec {
bool operator == (const FixedTinyVec<T, MaxSize> &other) const {
if (count_ != other.count_)
return false;
for (size_t i = 0; i < count_; i++) {
for (int i = 0; i < count_; i++) {
if (!(data_[i] == other.data_[i])) {
return false;
}

View file

@ -5,6 +5,8 @@
#include <cstdlib>
#include <cstdio>
#include <inttypes.h>
#ifndef _MSC_VER
#include <strings.h>
#endif
@ -192,7 +194,7 @@ void Section::Set(const char* key, uint32_t newValue) {
}
void Section::Set(const char* key, uint64_t newValue) {
Set(key, StringFromFormat("0x%016lx", newValue).c_str());
Set(key, StringFromFormat("0x%016" PRIx64, newValue).c_str());
}
void Section::Set(const char* key, float newValue) {

View file

@ -40,6 +40,7 @@ static const char * const g_categoryNames[(size_t)I18NCat::CATEGORY_COUNT] = {
"UI Elements",
"Upgrade",
"VR",
"Achievements",
};
I18NRepo g_i18nrepo;

View file

@ -56,6 +56,7 @@ enum class I18NCat : uint8_t {
UI_ELEMENTS,
UPGRADE,
VR,
ACHIEVEMENTS,
CATEGORY_COUNT,
NONE = CATEGORY_COUNT,
};

View file

@ -0,0 +1,206 @@
#include "Common/File/AndroidContentURI.h"
bool AndroidContentURI::Parse(const std::string &path) {
const char *prefix = "content://";
if (!startsWith(path, prefix)) {
return false;
}
std::string components = path.substr(strlen(prefix));
std::vector<std::string> parts;
SplitString(components, '/', parts);
if (parts.size() == 3) {
// Single file URI.
provider = parts[0];
if (parts[1] == "tree") {
// Single directory URI.
// Not sure when we encounter these?
// file empty signals this type.
root = UriDecode(parts[2]);
return true;
} else if (parts[1] == "document") {
// root empty signals this type.
file = UriDecode(parts[2]);
return true;
} else {
// What's this?
return false;
}
} else if (parts.size() == 5) {
// Tree URI
provider = parts[0];
if (parts[1] != "tree") {
return false;
}
root = UriDecode(parts[2]);
if (parts[3] != "document") {
return false;
}
file = UriDecode(parts[4]);
// Sanity check file path.
return startsWith(file, root);
} else {
// Invalid Content URI
return false;
}
}
AndroidContentURI AndroidContentURI::WithRootFilePath(const std::string &filePath) {
if (root.empty()) {
ERROR_LOG(SYSTEM, "WithRootFilePath cannot be used with single file URIs.");
return *this;
}
AndroidContentURI uri = *this;
uri.file = uri.root;
if (!filePath.empty()) {
uri.file += "/" + filePath;
}
return uri;
}
AndroidContentURI AndroidContentURI::WithComponent(const std::string &filePath) {
AndroidContentURI uri = *this;
if (uri.file.empty()) {
// Not sure what to do.
return uri;
}
if (uri.file.back() == ':') {
// Special case handling for Document URIs: Treat the ':' as a directory separator too (but preserved in the filename).
uri.file = uri.file + filePath;
} else {
uri.file = uri.file + "/" + filePath;
}
return uri;
}
AndroidContentURI AndroidContentURI::WithExtraExtension(const std::string &extension) {
AndroidContentURI uri = *this;
uri.file = uri.file + extension;
return uri;
}
AndroidContentURI AndroidContentURI::WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const {
_dbg_assert_(!oldExtension.empty() && oldExtension[0] == '.');
_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');
AndroidContentURI uri = *this;
if (endsWithNoCase(file, oldExtension)) {
uri.file = file.substr(0, file.size() - oldExtension.size()) + newExtension;
}
return uri;
}
AndroidContentURI AndroidContentURI::WithReplacedExtension(const std::string &newExtension) const {
_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');
AndroidContentURI uri = *this;
if (file.empty()) {
return uri;
}
std::string extension = GetFileExtension();
uri.file = file.substr(0, file.size() - extension.size()) + newExtension;
return uri;
}
bool AndroidContentURI::CanNavigateUp() const {
if (IsTreeURI()) {
return file.size() > root.size();
} else {
return file.find(':') != std::string::npos && file.back() != ':';
}
}
// Only goes downwards in hierarchies. No ".." will ever be generated.
bool AndroidContentURI::ComputePathTo(const AndroidContentURI &other, std::string &path) const {
size_t offset = FilePath().size() + 1;
std::string otherFilePath = other.FilePath();
if (offset >= otherFilePath.size()) {
ERROR_LOG(SYSTEM, "Bad call to PathTo. '%s' -> '%s'", FilePath().c_str(), other.FilePath().c_str());
return false;
}
path = other.FilePath().substr(FilePath().size() + 1);
return true;
}
std::string AndroidContentURI::GetFileExtension() const {
size_t pos = file.rfind(".");
if (pos == std::string::npos) {
return "";
}
size_t slash_pos = file.rfind("/");
if (slash_pos != std::string::npos && slash_pos > pos) {
// Don't want to detect "df/file" from "/as.df/file"
return "";
}
std::string ext = file.substr(pos);
for (size_t i = 0; i < ext.size(); i++) {
ext[i] = tolower(ext[i]);
}
return ext;
}
std::string AndroidContentURI::GetLastPart() const {
if (file.empty()) {
// Can't do anything anyway.
return std::string();
}
if (!CanNavigateUp()) {
size_t colon = file.rfind(':');
if (colon == std::string::npos) {
return std::string();
}
if (file.back() == ':') {
return file;
}
return file.substr(colon + 1);
}
size_t slash = file.rfind('/');
if (slash == std::string::npos) {
// ok, look for the final colon. If it's the last char, we would have been caught above in !CanNavigateUp.
size_t colon = file.rfind(':');
if (colon == std::string::npos) {
return std::string();
}
return file.substr(colon + 1);
}
std::string part = file.substr(slash + 1);
return part;
}
bool AndroidContentURI::NavigateUp() {
if (!CanNavigateUp()) {
return false;
}
size_t slash = file.rfind('/');
if (slash == std::string::npos) {
// ok, look for the final colon.
size_t colon = file.rfind(':');
if (colon == std::string::npos) {
return false;
}
file = file.substr(0, colon + 1); // Note: we include the colon in these paths.
return true;
}
file = file.substr(0, slash);
return true;
}
std::string AndroidContentURI::ToString() const {
if (file.empty()) {
// Tree URI
return StringFromFormat("content://%s/tree/%s", provider.c_str(), UriEncode(root).c_str());
} else if (root.empty()) {
// Single file URI
return StringFromFormat("content://%s/document/%s", provider.c_str(), UriEncode(file).c_str());
} else {
// File URI from Tree
return StringFromFormat("content://%s/tree/%s/document/%s", provider.c_str(), UriEncode(root).c_str(), UriEncode(file).c_str());
}
}

View file

@ -0,0 +1,73 @@
#pragma once
#include <string>
#include "Common/StringUtils.h"
#include "Common/Net/URL.h"
#include "Common/Log.h"
// Utility to deal with Android storage URIs of the forms:
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO
// This file compiles on all platforms, to reduce the need for ifdefs.
// I am not 100% sure it's OK to rely on the internal format of file content URIs.
// On the other hand, I'm sure tons of apps would break if these changed, so I think we can
// consider them pretty stable. Additionally, the official Document library just manipulates the URIs
// in similar ways...
class AndroidContentURI {
private:
std::string provider;
std::string root;
std::string file;
public:
AndroidContentURI() {}
explicit AndroidContentURI(const std::string &path) {
Parse(path);
}
bool Parse(const std::string &path);
AndroidContentURI WithRootFilePath(const std::string &filePath);
AndroidContentURI WithComponent(const std::string &filePath);
AndroidContentURI WithExtraExtension(const std::string &extension);
AndroidContentURI WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const;
AndroidContentURI WithReplacedExtension(const std::string &newExtension) const;
bool CanNavigateUp() const;
// Only goes downwards in hierarchies. No ".." will ever be generated.
bool ComputePathTo(const AndroidContentURI &other, std::string &path) const;
std::string GetFileExtension() const;
std::string GetLastPart() const;
bool NavigateUp();
bool TreeContains(const AndroidContentURI &fileURI) {
if (root.empty()) {
return false;
}
return startsWith(fileURI.file, root);
}
std::string ToString() const;
// Never store the output of this, only show it to the user.
std::string ToVisualString() const {
return file;
}
const std::string &FilePath() const {
return file;
}
const std::string &RootPath() const {
return root.empty() ? file : root;
}
bool IsTreeURI() const {
return !root.empty();
}
};

View file

@ -25,7 +25,6 @@
#include "ppsspp_config.h"
#include "android/jni/app-android.h"
#include "android/jni/AndroidContentURI.h"
#ifdef __MINGW32__
#include <unistd.h>
@ -33,12 +32,14 @@
#define _POSIX_THREAD_SAFE_FUNCTIONS 200112L
#endif
#endif
#include <cstring>
#include <ctime>
#include <memory>
#include "Common/Log.h"
#include "Common/LogReporting.h"
#include "Common/File/AndroidContentURI.h"
#include "Common/File/FileUtil.h"
#include "Common/StringUtils.h"
#include "Common/SysError.h"

View file

@ -4,13 +4,13 @@
#include <cstring>
#include "Common/File/Path.h"
#include "Common/File/AndroidContentURI.h"
#include "Common/File/FileUtil.h"
#include "Common/StringUtils.h"
#include "Common/Log.h"
#include "Common/Data/Encoding/Utf8.h"
#include "android/jni/app-android.h"
#include "android/jni/AndroidContentURI.h"
#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)
#include "UWP/UWPHelpers/StorageManager.h"
@ -161,7 +161,7 @@ std::string Path::GetFilename() const {
return path_;
}
static std::string GetExtFromString(const std::string &str) {
std::string GetExtFromString(const std::string &str) {
size_t pos = str.rfind(".");
if (pos == std::string::npos) {
return "";
@ -181,7 +181,7 @@ static std::string GetExtFromString(const std::string &str) {
std::string Path::GetFileExtension() const {
if (type_ == PathType::CONTENT_URI) {
AndroidContentURI uri(path_);
return GetExtFromString(uri.FilePath());
return uri.GetFileExtension();
}
return GetExtFromString(path_);
}
@ -262,6 +262,15 @@ std::wstring Path::ToWString() const {
}
return w;
}
std::string Path::ToCString() const {
std::string w = path_;
for (size_t i = 0; i < w.size(); i++) {
if (w[i] == '/') {
w[i] = '\\';
}
}
return w;
}
#endif
std::string Path::ToVisualString(const char *relativeRoot) const {

View file

@ -90,6 +90,11 @@ public:
#if PPSSPP_PLATFORM(WINDOWS)
std::wstring ToWString() const;
std::string ToCString() const; // Flips the slashes back to Windows standard, but string still UTF-8.
#else
std::string ToCString() const {
return ToString();
}
#endif
// Pass in a relative root to turn the path into a relative path - if it is one!
@ -133,6 +138,8 @@ private:
PathType type_;
};
// Utility function for parsing out file extensions.
std::string GetExtFromString(const std::string &str);
// Utility function for fixing the case of paths. Only present on Unix-like systems.

View file

@ -17,7 +17,6 @@
#if PPSSPP_PLATFORM(ANDROID)
#include "android/jni/app-android.h"
#include "android/jni/AndroidContentURI.h"
#endif
bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *cancel, std::vector<File::FileInfo> &files) {

View file

@ -4,6 +4,7 @@
#include "Common/File/VFS/VFS.h"
#include "Common/File/FileUtil.h"
#include "Common/File/AndroidStorage.h"
#include "Common/StringUtils.h"
VFS g_VFS;
@ -27,7 +28,7 @@ void VFS::Clear() {
static bool IsLocalAbsolutePath(const char *path) {
bool isUnixLocal = path[0] == '/';
#ifdef _WIN32
bool isWindowsLocal = isalpha(path[0]) && path[1] == ':';
bool isWindowsLocal = (isalpha(path[0]) && path[1] == ':') || startsWith(path, "\\\\") || startsWith(path, "//");
#else
bool isWindowsLocal = false;
#endif

View file

@ -92,6 +92,7 @@ public:
Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override;
void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override;
void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override;
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override;
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) override;
@ -130,7 +131,6 @@ public:
stencilDirty_ = true;
}
void EndFrame() override;
void Draw(int vertexCount, int offset) override;
void DrawIndexed(int vertexCount, int offset) override;
@ -138,6 +138,9 @@ public:
void Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) override;
void BeginFrame() override;
void EndFrame() override;
int GetFrameCount() override { return frameCount_; }
std::string GetInfoString(InfoField info) const override {
switch (info) {
@ -220,6 +223,7 @@ private:
int nextIndexBufferOffset_ = 0;
InvalidationCallback invalidationCallback_;
int frameCount_ = 0;
// Dynamic state
float blendFactor_[4]{};
@ -422,6 +426,7 @@ void D3D11DrawContext::HandleEvent(Event ev, int width, int height, void *param1
void D3D11DrawContext::EndFrame() {
curPipeline_ = nullptr;
frameCount_++;
}
void D3D11DrawContext::SetViewport(const Viewport &viewport) {
@ -795,35 +800,83 @@ public:
width_ = desc.width;
height_ = desc.height;
depth_ = desc.depth;
format_ = desc.format;
mipLevels_ = desc.mipLevels;
}
~D3D11Texture() {
if (tex)
tex->Release();
if (stagingTex)
stagingTex->Release();
if (view)
view->Release();
if (tex_)
tex_->Release();
if (stagingTex_)
stagingTex_->Release();
if (view_)
view_->Release();
}
ID3D11Texture2D *tex = nullptr;
ID3D11Texture2D *stagingTex = nullptr;
ID3D11ShaderResourceView *view = nullptr;
bool Create(ID3D11DeviceContext *context, ID3D11Device *device, const TextureDesc &desc, bool generateMips);
bool CreateStagingTexture(ID3D11Device *device);
void UpdateTextureLevels(ID3D11DeviceContext *context, ID3D11Device *device, Texture *texture, const uint8_t *const *data, TextureCallback initDataCallback, int numLevels);
ID3D11ShaderResourceView *View() { return view_; }
private:
bool FillLevel(ID3D11DeviceContext *context, int level, int w, int h, int d, const uint8_t *const *data, TextureCallback initDataCallback);
ID3D11Texture2D *tex_ = nullptr;
ID3D11Texture2D *stagingTex_ = nullptr;
ID3D11ShaderResourceView *view_ = nullptr;
int mipLevels_ = 0;
};
Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
if (!(GetDataFormatSupport(desc.format) & FMT_TEXTURE)) {
// D3D11 does not support this format as a texture format.
return nullptr;
bool D3D11Texture::FillLevel(ID3D11DeviceContext *context, int level, int w, int h, int d, const uint8_t *const *data, TextureCallback initDataCallback) {
D3D11_MAPPED_SUBRESOURCE mapped;
HRESULT hr = context->Map(stagingTex_, level, D3D11_MAP_WRITE, 0, &mapped);
if (!SUCCEEDED(hr)) {
tex_->Release();
tex_ = nullptr;
return false;
}
D3D11Texture *tex = new D3D11Texture(desc);
bool generateMips = desc.generateMips;
if (desc.generateMips && !(GetDataFormatSupport(desc.format) & FMT_AUTOGEN_MIPS)) {
// D3D11 does not support autogenerating mipmaps for this format.
generateMips = false;
if (!initDataCallback((uint8_t *)mapped.pData, data[level], w, h, d, mapped.RowPitch, mapped.DepthPitch)) {
for (int s = 0; s < d; ++s) {
for (int y = 0; y < h; ++y) {
void *dest = (uint8_t *)mapped.pData + mapped.DepthPitch * s + mapped.RowPitch * y;
uint32_t byteStride = w * (uint32_t)DataFormatSizeInBytes(format_);
const void *src = data[level] + byteStride * (y + h * d);
memcpy(dest, src, byteStride);
}
}
}
context->Unmap(stagingTex_, level);
return true;
}
bool D3D11Texture::CreateStagingTexture(ID3D11Device *device) {
if (stagingTex_)
return true;
D3D11_TEXTURE2D_DESC descColor{};
descColor.Width = width_;
descColor.Height = height_;
descColor.MipLevels = mipLevels_;
descColor.ArraySize = 1;
descColor.Format = dataFormatToD3D11(format_);
descColor.SampleDesc.Count = 1;
descColor.SampleDesc.Quality = 0;
descColor.Usage = D3D11_USAGE_STAGING;
descColor.BindFlags = 0;
descColor.MiscFlags = 0;
descColor.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
HRESULT hr = device->CreateTexture2D(&descColor, nullptr, &stagingTex_);
if (!SUCCEEDED(hr)) {
stagingTex_->Release();
stagingTex_ = nullptr;
return false;
}
return true;
}
bool D3D11Texture::Create(ID3D11DeviceContext *context, ID3D11Device *device, const TextureDesc &desc, bool generateMips) {
D3D11_TEXTURE2D_DESC descColor{};
descColor.Width = desc.width;
descColor.Height = desc.height;
@ -832,25 +885,16 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
descColor.Format = dataFormatToD3D11(desc.format);
descColor.SampleDesc.Count = 1;
descColor.SampleDesc.Quality = 0;
if (desc.initDataCallback) {
descColor.Usage = D3D11_USAGE_STAGING;
descColor.BindFlags = 0;
descColor.MiscFlags = 0;
descColor.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
HRESULT hr = device_->CreateTexture2D(&descColor, nullptr, &tex->stagingTex);
if (!SUCCEEDED(hr)) {
delete tex;
return nullptr;
}
}
descColor.Usage = D3D11_USAGE_DEFAULT;
descColor.BindFlags = generateMips ? (D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET) : D3D11_BIND_SHADER_RESOURCE;
descColor.MiscFlags = generateMips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0;
descColor.CPUAccessFlags = 0;
// Make sure we have a staging texture if we'll need it.
if (desc.initDataCallback && !CreateStagingTexture(device)) {
return false;
}
D3D11_SUBRESOURCE_DATA *initDataParam = nullptr;
D3D11_SUBRESOURCE_DATA initData[12]{};
std::vector<uint8_t> initDataBuffer[12];
@ -870,62 +914,39 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
initDataParam = initData;
}
HRESULT hr = device_->CreateTexture2D(&descColor, initDataParam, &tex->tex);
HRESULT hr = device->CreateTexture2D(&descColor, initDataParam, &tex_);
if (!SUCCEEDED(hr)) {
delete tex;
return nullptr;
tex_ = nullptr;
return false;
}
hr = device_->CreateShaderResourceView(tex->tex, nullptr, &tex->view);
if (!SUCCEEDED(hr)) {
delete tex;
return nullptr;
}
auto populateLevelCallback = [&](int level, int w, int h, int d) {
D3D11_MAPPED_SUBRESOURCE mapped;
hr = context_->Map(tex->stagingTex, level, D3D11_MAP_WRITE, 0, &mapped);
hr = device->CreateShaderResourceView(tex_, nullptr, &view_);
if (!SUCCEEDED(hr)) {
return false;
}
if (!desc.initDataCallback((uint8_t *)mapped.pData, desc.initData[level], w, h, d, mapped.RowPitch, mapped.DepthPitch)) {
for (int s = 0; s < d; ++s) {
for (int y = 0; y < h; ++y) {
void *dest = (uint8_t *)mapped.pData + mapped.DepthPitch * s + mapped.RowPitch * y;
uint32_t byteStride = w * (uint32_t)DataFormatSizeInBytes(desc.format);
const void *src = desc.initData[level] + byteStride * (y + h * d);
memcpy(dest, src, byteStride);
}
}
}
context_->Unmap(tex->stagingTex, level);
return true;
};
if (generateMips && desc.initData.size() >= 1) {
if (desc.initDataCallback) {
if (!populateLevelCallback(0, desc.width, desc.height, desc.depth)) {
delete tex;
return nullptr;
if (!FillLevel(context, 0, desc.width, desc.height, desc.depth, desc.initData.data(), desc.initDataCallback)) {
tex_->Release();
return false;
}
context_->CopyResource(tex->stagingTex, tex->stagingTex);
tex->stagingTex->Release();
tex->stagingTex = nullptr;
context->CopyResource(tex_, stagingTex_);
stagingTex_->Release();
stagingTex_ = nullptr;
} else {
uint32_t byteStride = desc.width * (uint32_t)DataFormatSizeInBytes(desc.format);
context_->UpdateSubresource(tex->tex, 0, nullptr, desc.initData[0], byteStride, 0);
context->UpdateSubresource(tex_, 0, nullptr, desc.initData[0], byteStride, 0);
}
context_->GenerateMips(tex->view);
context->GenerateMips(view_);
} else if (desc.initDataCallback) {
int w = desc.width;
int h = desc.height;
int d = desc.depth;
for (int i = 0; i < (int)desc.initData.size(); i++) {
if (!populateLevelCallback(i, desc.width, desc.height, desc.depth)) {
if (!FillLevel(context, i, w, h, d, desc.initData.data(), desc.initDataCallback)) {
if (i == 0) {
delete tex;
return nullptr;
return false;
} else {
break;
}
@ -936,13 +957,62 @@ Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
d = (d + 1) / 2;
}
context_->CopyResource(tex->tex, tex->stagingTex);
tex->stagingTex->Release();
tex->stagingTex = nullptr;
context->CopyResource(tex_, stagingTex_);
stagingTex_->Release();
stagingTex_ = nullptr;
}
return true;
}
void D3D11Texture::UpdateTextureLevels(ID3D11DeviceContext *context, ID3D11Device *device, Texture *texture, const uint8_t * const*data, TextureCallback initDataCallback, int numLevels) {
if (!CreateStagingTexture(device)) {
return;
}
int w = width_;
int h = height_;
int d = depth_;
for (int i = 0; i < (int)numLevels; i++) {
if (!FillLevel(context, i, w, h, d, data, initDataCallback)) {
break;
}
w = (w + 1) / 2;
h = (h + 1) / 2;
d = (d + 1) / 2;
}
context->CopyResource(tex_, stagingTex_);
stagingTex_->Release();
stagingTex_ = nullptr;
}
Texture *D3D11DrawContext::CreateTexture(const TextureDesc &desc) {
if (!(GetDataFormatSupport(desc.format) & FMT_TEXTURE)) {
// D3D11 does not support this format as a texture format.
return nullptr;
}
D3D11Texture *tex = new D3D11Texture(desc);
bool generateMips = desc.generateMips;
if (desc.generateMips && !(GetDataFormatSupport(desc.format) & FMT_AUTOGEN_MIPS)) {
// D3D11 does not support autogenerating mipmaps for this format.
generateMips = false;
}
if (!tex->Create(context_, device_, desc, generateMips)) {
tex->Release();
return nullptr;
}
return tex;
}
void D3D11DrawContext::UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) {
D3D11Texture *tex = (D3D11Texture *)texture;
tex->UpdateTextureLevels(context_, device_, texture, data, initDataCallback, numLevels);
}
ShaderModule *D3D11DrawContext::CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t dataSize, const char *tag) {
if (language != ShaderLanguage::HLSL_D3D11) {
ERROR_LOG(G3D, "Unsupported shader language");
@ -1411,7 +1481,7 @@ void D3D11DrawContext::BindTextures(int start, int count, Texture **textures, Te
_assert_(start + count <= ARRAY_SIZE(views));
for (int i = 0; i < count; i++) {
D3D11Texture *tex = (D3D11Texture *)textures[i];
views[i] = tex ? tex->view : nullptr;
views[i] = tex ? tex->View() : nullptr;
}
context_->PSSetShaderResources(start, count, views);
}
@ -1771,7 +1841,7 @@ uint64_t D3D11DrawContext::GetNativeObject(NativeObject obj, void *srcObject) {
case NativeObject::FEATURE_LEVEL:
return (uint64_t)(uintptr_t)featureLevel_;
case NativeObject::TEXTURE_VIEW:
return (uint64_t)(((D3D11Texture *)srcObject)->view);
return (uint64_t)(((D3D11Texture *)srcObject)->View());
default:
return 0;
}

View file

@ -308,14 +308,14 @@ public:
return nullptr;
}
}
void UpdateTextureLevels(const uint8_t * const *data, int numLevels, TextureCallback initDataCallback);
private:
void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback callback);
void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback initDataCallback);
bool Create(const TextureDesc &desc);
LPDIRECT3DDEVICE9 device_;
LPDIRECT3DDEVICE9EX deviceEx_;
TextureType type_;
DataFormat format_;
D3DFORMAT d3dfmt_;
LPDIRECT3DTEXTURE9 tex_ = nullptr;
LPDIRECT3DVOLUMETEXTURE9 volTex_ = nullptr;
@ -374,25 +374,29 @@ bool D3D9Texture::Create(const TextureDesc &desc) {
break;
}
if (FAILED(hr)) {
ERROR_LOG(G3D, "Texture creation failed");
ERROR_LOG(G3D, "D3D9 Texture creation failed");
return false;
}
if (desc.initData.size()) {
// In D3D9, after setting D3DUSAGE_AUTOGENMIPS, we can only access the top layer. The rest will be
// automatically generated.
int maxLevel = desc.generateMips ? 1 : (int)desc.initData.size();
int w = desc.width;
int h = desc.height;
int d = desc.depth;
for (int i = 0; i < maxLevel; i++) {
SetImageData(0, 0, 0, w, h, d, i, 0, desc.initData[i], desc.initDataCallback);
int numLevels = desc.generateMips ? 1 : (int)desc.initData.size();
UpdateTextureLevels(desc.initData.data(), numLevels, desc.initDataCallback);
}
return true;
}
void D3D9Texture::UpdateTextureLevels(const uint8_t * const *data, int numLevels, TextureCallback initDataCallback) {
int w = width_;
int h = height_;
int d = depth_;
for (int i = 0; i < numLevels; i++) {
SetImageData(0, 0, 0, w, h, d, i, 0, data[i], initDataCallback);
w = (w + 1) / 2;
h = (h + 1) / 2;
d = (d + 1) / 2;
}
}
return true;
}
// Just switches R and G.
@ -532,6 +536,7 @@ public:
Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override;
void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override;
void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override;
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override {
// Not implemented
@ -575,6 +580,7 @@ public:
}
void EndFrame() override;
int GetFrameCount() override { return frameCount_; }
void UpdateDynamicUniformBuffer(const void *ub, size_t size) override;
@ -635,6 +641,7 @@ private:
D3DCAPS9 d3dCaps_;
char shadeLangVersion_[64]{};
DeviceCaps caps_{};
int frameCount_ = 0;
// Bound state
AutoRef<D3D9Pipeline> curPipeline_;
@ -934,6 +941,12 @@ Texture *D3D9Context::CreateTexture(const TextureDesc &desc) {
return tex;
}
void D3D9Context::UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) {
D3D9Texture *tex = (D3D9Texture *)texture;
tex->UpdateTextureLevels(data, numLevels, initDataCallback);
}
void D3D9Context::BindTextures(int start, int count, Texture **textures, TextureBindFlags flags) {
_assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = start; i < start + count; i++) {
@ -953,6 +966,7 @@ void D3D9Context::BindNativeTexture(int index, void *nativeTexture) {
void D3D9Context::EndFrame() {
curPipeline_ = nullptr;
frameCount_++;
}
static void SemanticToD3D9UsageAndIndex(int semantic, BYTE *usage, BYTE *index) {

View file

@ -0,0 +1,28 @@
#include <mutex>
#include <set>
#include "Common/GPU/GPUBackendCommon.h"
// Global push buffer tracker for GPU memory profiling.
// Don't want to manually dig up all the active push buffers.
static std::mutex g_pushBufferListMutex;
static std::set<GPUMemoryManager *> g_pushBuffers;
std::vector<GPUMemoryManager *> GetActiveGPUMemoryManagers() {
std::vector<GPUMemoryManager *> buffers;
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
for (auto iter : g_pushBuffers) {
buffers.push_back(iter);
}
return buffers;
}
void RegisterGPUMemoryManager(GPUMemoryManager *manager) {
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.insert(manager);
}
void UnregisterGPUMemoryManager(GPUMemoryManager *manager) {
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.erase(manager);
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <vector>
// Just an abstract thing to get debug information.
class GPUMemoryManager {
public:
virtual ~GPUMemoryManager() {}
virtual void GetDebugString(char *buffer, size_t bufSize) const = 0;
virtual const char *Name() const = 0; // for sorting
};
std::vector<GPUMemoryManager *> GetActiveGPUMemoryManagers();
void RegisterGPUMemoryManager(GPUMemoryManager *manager);
void UnregisterGPUMemoryManager(GPUMemoryManager *manager);

View file

@ -55,7 +55,7 @@ bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuin
internalFormat = GL_RGB;
format = GL_RGB;
type = GL_UNSIGNED_BYTE;
alignment = 1;
alignment = 3;
break;
case DataFormat::R4G4B4A4_UNORM_PACK16:

View file

@ -626,11 +626,13 @@ bool CheckGLExtensions() {
}
void SetGLCoreContext(bool flag) {
_assert_msg_(!extensionsDone, "SetGLCoreContext() after CheckGLExtensions()");
if (!extensionsDone) {
useCoreContext = flag;
// For convenience, it'll get reset later.
gl_extensions.IsCoreContext = useCoreContext;
} else {
_assert_(flag == useCoreContext);
}
}
void ResetGLExtensions() {

View file

@ -3,6 +3,7 @@
#include <mutex>
#include <condition_variable>
#include <vector>
#include <string>
#include <set>
#include "Common/GPU/OpenGL/GLCommon.h"
@ -35,6 +36,15 @@ public:
std::vector<GLPushBuffer *> pushBuffers;
};
struct GLQueueProfileContext {
bool enabled;
double cpuStartTime;
double cpuEndTime;
std::string passesString;
int commandCounts[25]; // Can't grab count from the enum as it would mean a circular include. Might clean this up later.
};
// Per-frame data, round-robin so we can overlap submission with execution of the previous frame.
struct GLFrameData {
bool skipSwap = false;
@ -49,4 +59,6 @@ struct GLFrameData {
GLDeleter deleter;
GLDeleter deleter_prev;
std::set<GLPushBuffer *> activePushBuffers;
GLQueueProfileContext profile;
};

View file

@ -0,0 +1,288 @@
#include "Common/MemoryUtil.h"
#include "Common/GPU/OpenGL/GLMemory.h"
#include "Common/GPU/OpenGL/GLRenderManager.h"
#include "Common/GPU/OpenGL/GLFeatures.h"
#include "Common/Data/Text/Parsers.h"
extern std::thread::id renderThreadId;
#if MAX_LOGLEVEL >= DEBUG_LEVEL
static bool OnRenderThread() {
return std::this_thread::get_id() == renderThreadId;
}
#endif
void *GLRBuffer::Map(GLBufferStrategy strategy) {
_assert_(buffer_ != 0);
GLbitfield access = GL_MAP_WRITE_BIT;
if ((strategy & GLBufferStrategy::MASK_FLUSH) != 0) {
access |= GL_MAP_FLUSH_EXPLICIT_BIT;
}
if ((strategy & GLBufferStrategy::MASK_INVALIDATE) != 0) {
access |= GL_MAP_INVALIDATE_BUFFER_BIT;
}
void *p = nullptr;
bool allowNativeBuffer = strategy != GLBufferStrategy::SUBDATA;
if (allowNativeBuffer) {
glBindBuffer(target_, buffer_);
if (gl_extensions.ARB_buffer_storage || gl_extensions.EXT_buffer_storage) {
#if !PPSSPP_PLATFORM(IOS)
if (!hasStorage_) {
GLbitfield storageFlags = access & ~(GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
#ifdef USING_GLES2
#ifdef GL_EXT_buffer_storage
glBufferStorageEXT(target_, size_, nullptr, storageFlags);
#endif
#else
glBufferStorage(target_, size_, nullptr, storageFlags);
#endif
hasStorage_ = true;
}
#endif
p = glMapBufferRange(target_, 0, size_, access);
} else if (gl_extensions.VersionGEThan(3, 0, 0)) {
// GLES3 or desktop 3.
p = glMapBufferRange(target_, 0, size_, access);
} else if (!gl_extensions.IsGLES) {
#ifndef USING_GLES2
p = glMapBuffer(target_, GL_READ_WRITE);
#endif
}
}
mapped_ = p != nullptr;
return p;
}
bool GLRBuffer::Unmap() {
glBindBuffer(target_, buffer_);
mapped_ = false;
return glUnmapBuffer(target_) == GL_TRUE;
}
GLPushBuffer::GLPushBuffer(GLRenderManager *render, GLuint target, size_t size, const char *tag) : render_(render), size_(size), target_(target), tag_(tag) {
bool res = AddBuffer();
_assert_(res);
RegisterGPUMemoryManager(this);
}
GLPushBuffer::~GLPushBuffer() {
UnregisterGPUMemoryManager(this);
Destroy(true);
}
void GLPushBuffer::Map() {
_assert_(!writePtr_);
auto &info = buffers_[buf_];
writePtr_ = info.deviceMemory ? info.deviceMemory : info.localMemory;
info.flushOffset = 0;
// Force alignment. This is needed for PushAligned() to work as expected.
while ((intptr_t)writePtr_ & 15) {
writePtr_++;
offset_++;
info.flushOffset++;
}
_assert_(writePtr_);
}
void GLPushBuffer::Unmap() {
_assert_(writePtr_);
if (!buffers_[buf_].deviceMemory) {
// Here we simply upload the data to the last buffer.
// Might be worth trying with size_ instead of offset_, so the driver can replace
// the whole buffer. At least if it's close.
render_->BufferSubdata(buffers_[buf_].buffer, 0, offset_, buffers_[buf_].localMemory, false);
} else {
buffers_[buf_].flushOffset = offset_;
}
writePtr_ = nullptr;
}
void GLPushBuffer::Flush() {
// Must be called from the render thread.
_dbg_assert_(OnRenderThread());
if (buf_ >= buffers_.size()) {
_dbg_assert_msg_(false, "buf_ somehow got out of sync: %d vs %d", (int)buf_, (int)buffers_.size());
return;
}
buffers_[buf_].flushOffset = offset_;
if (!buffers_[buf_].deviceMemory && writePtr_) {
auto &info = buffers_[buf_];
if (info.flushOffset != 0) {
_assert_(info.buffer->buffer_);
glBindBuffer(target_, info.buffer->buffer_);
glBufferSubData(target_, 0, info.flushOffset, info.localMemory);
}
// Here we will submit all the draw calls, with the already known buffer and offsets.
// Might as well reset the write pointer here and start over the current buffer.
writePtr_ = info.localMemory;
offset_ = 0;
info.flushOffset = 0;
}
// For device memory, we flush all buffers here.
if ((strategy_ & GLBufferStrategy::MASK_FLUSH) != 0) {
for (auto &info : buffers_) {
if (info.flushOffset == 0 || !info.deviceMemory)
continue;
glBindBuffer(target_, info.buffer->buffer_);
glFlushMappedBufferRange(target_, 0, info.flushOffset);
info.flushOffset = 0;
}
}
}
bool GLPushBuffer::AddBuffer() {
// INFO_LOG(G3D, "GLPushBuffer(%s): Allocating %d bytes", tag_, size_);
BufInfo info;
info.localMemory = (uint8_t *)AllocateAlignedMemory(size_, 16);
if (!info.localMemory)
return false;
info.buffer = render_->CreateBuffer(target_, size_, GL_DYNAMIC_DRAW);
info.size = size_;
buf_ = buffers_.size();
buffers_.push_back(info);
return true;
}
void GLPushBuffer::Destroy(bool onRenderThread) {
if (buf_ == -1)
return; // Already destroyed
for (BufInfo &info : buffers_) {
// This will automatically unmap device memory, if needed.
// NOTE: We immediately delete the buffer, don't go through the deleter, if we're on the render thread.
if (onRenderThread) {
delete info.buffer;
} else {
render_->DeleteBuffer(info.buffer);
}
FreeAlignedMemory(info.localMemory);
}
buffers_.clear();
buf_ = -1;
}
void GLPushBuffer::NextBuffer(size_t minSize) {
// First, unmap the current memory.
Unmap();
buf_++;
if (buf_ >= buffers_.size() || minSize > size_) {
// Before creating the buffer, adjust to the new size_ if necessary.
while (size_ < minSize) {
size_ <<= 1;
}
bool res = AddBuffer();
_assert_(res);
if (!res) {
// Let's try not to crash at least?
buf_ = 0;
}
}
// Now, move to the next buffer and map it.
offset_ = 0;
Map();
}
void GLPushBuffer::Defragment() {
_dbg_assert_msg_(!OnRenderThread(), "Defragment must not run on the render thread");
if (buffers_.size() <= 1) {
// Let's take this opportunity to jettison any localMemory we don't need.
for (auto &info : buffers_) {
if (info.deviceMemory) {
FreeAlignedMemory(info.localMemory);
info.localMemory = nullptr;
}
}
return;
}
// Okay, we have more than one. Destroy them all and start over with a larger one.
// When calling AddBuffer, we sometimes increase size_. So if we allocated multiple buffers in a frame,
// they won't all have the same size. Sum things up properly.
size_t newSize = 0;
for (int i = 0; i < (int)buffers_.size(); i++) {
newSize += buffers_[i].size;
}
Destroy(false);
// Set some sane but very free limits. If there's another spike, we'll just allocate more anyway.
size_ = std::min(std::max(newSize, (size_t)65536), (size_t)(512 * 1024 * 1024));
bool res = AddBuffer();
_assert_msg_(res, "AddBuffer failed");
}
size_t GLPushBuffer::GetTotalSize() const {
size_t sum = 0;
// When calling AddBuffer, we sometimes increase size_. So if we allocated multiple buffers in a frame,
// they won't all have the same size. Sum things up properly.
if (buffers_.size() > 1) {
for (int i = 0; i < (int)buffers_.size() - 1; i++) {
sum += buffers_[i].size;
}
}
sum += offset_;
return sum;
}
void GLPushBuffer::MapDevice(GLBufferStrategy strategy) {
_dbg_assert_msg_(OnRenderThread(), "MapDevice must run on render thread");
strategy_ = strategy;
if (strategy_ == GLBufferStrategy::SUBDATA) {
return;
}
bool mapChanged = false;
for (auto &info : buffers_) {
if (!info.buffer->buffer_ || info.deviceMemory) {
// Can't map - no device buffer associated yet or already mapped.
continue;
}
info.deviceMemory = (uint8_t *)info.buffer->Map(strategy_);
mapChanged = mapChanged || info.deviceMemory != nullptr;
if (!info.deviceMemory && !info.localMemory) {
// Somehow it failed, let's dodge crashing.
info.localMemory = (uint8_t *)AllocateAlignedMemory(info.buffer->size_, 16);
mapChanged = true;
}
_dbg_assert_msg_(info.localMemory || info.deviceMemory, "Local or device memory must succeed");
}
if (writePtr_ && mapChanged) {
// This can happen during a sync. Remap.
writePtr_ = nullptr;
Map();
}
}
void GLPushBuffer::UnmapDevice() {
_dbg_assert_msg_(OnRenderThread(), "UnmapDevice must run on render thread");
for (auto &info : buffers_) {
if (info.deviceMemory) {
// TODO: Technically this can return false?
info.buffer->Unmap();
info.deviceMemory = nullptr;
}
}
}
void GLPushBuffer::GetDebugString(char *buffer, size_t bufSize) const {
snprintf(buffer, bufSize, "%s: %s/%s (%d)", tag_, NiceSizeFormat(this->offset_).c_str(), NiceSizeFormat(this->size_).c_str(), (int)buffers_.size());
}

View file

@ -0,0 +1,185 @@
#pragma once
#include <vector>
#include <cstdint>
#include <cstring>
#include "Common/GPU/GPUBackendCommon.h"
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/Log.h"
enum class GLBufferStrategy {
SUBDATA = 0,
MASK_FLUSH = 0x10,
MASK_INVALIDATE = 0x20,
// Map/unmap the buffer each frame.
FRAME_UNMAP = 1,
// Map/unmap and also invalidate the buffer on map.
INVALIDATE_UNMAP = MASK_INVALIDATE,
// Map/unmap and explicitly flushed changed ranges.
FLUSH_UNMAP = MASK_FLUSH,
// Map/unmap, invalidate on map, and explicit flush.
FLUSH_INVALIDATE_UNMAP = MASK_FLUSH | MASK_INVALIDATE,
};
static inline int operator &(const GLBufferStrategy &lhs, const GLBufferStrategy &rhs) {
return (int)lhs & (int)rhs;
}
class GLRBuffer {
public:
GLRBuffer(GLuint target, size_t size) : target_(target), size_((int)size) {}
~GLRBuffer() {
if (buffer_) {
glDeleteBuffers(1, &buffer_);
}
}
void *Map(GLBufferStrategy strategy);
bool Unmap();
bool Mapped() const {
return mapped_;
}
GLuint buffer_ = 0;
GLuint target_;
int size_;
private:
bool mapped_ = false;
bool hasStorage_ = false;
};
class GLRenderManager;
// Similar to VulkanPushBuffer but is currently less efficient - it collects all the data in
// RAM then does a big memcpy/buffer upload at the end of the frame. This is at least a lot
// faster than the hundreds of buffer uploads or memory array buffers we used before.
// On modern GL we could avoid the copy using glBufferStorage but not sure it's worth the
// trouble.
// We need to manage the lifetime of this together with the other resources so its destructor
// runs on the render thread.
class GLPushBuffer : public GPUMemoryManager {
public:
friend class GLRenderManager;
struct BufInfo {
GLRBuffer *buffer = nullptr;
uint8_t *localMemory = nullptr;
uint8_t *deviceMemory = nullptr;
size_t flushOffset = 0;
size_t size;
};
GLPushBuffer(GLRenderManager *render, GLuint target, size_t size, const char *tag);
~GLPushBuffer();
void Reset() { offset_ = 0; }
void GetDebugString(char *buffer, size_t bufSize) const override;
const char *Name() const override { return tag_; }; // for sorting
// Utility for users of this class, not used internally.
enum { INVALID_OFFSET = 0xFFFFFFFF };
private:
// Needs context in case of defragment.
void Begin() {
buf_ = 0;
offset_ = 0;
// Note: we must defrag because some buffers may be smaller than size_.
Defragment();
Map();
_dbg_assert_(writePtr_);
}
void BeginNoReset() {
Map();
}
void End() {
Unmap();
}
public:
void Map();
void Unmap();
bool IsReady() const {
return writePtr_ != nullptr;
}
// Recommended - lets you write directly into the buffer through the returned pointer.
// If you didn't end up using all the memory you grabbed here, then before calling Allocate or Push
// again, call Rewind (see below).
uint8_t *Allocate(uint32_t numBytes, uint32_t alignment, GLRBuffer **buf, uint32_t *bindOffset) {
uint32_t offset = ((uint32_t)offset_ + alignment - 1) & ~(alignment - 1);
if (offset + numBytes <= size_) {
// Common path.
offset_ = offset + numBytes;
*buf = buffers_[buf_].buffer;
*bindOffset = offset;
return writePtr_ + offset;
}
NextBuffer(numBytes);
*bindOffset = 0;
*buf = buffers_[buf_].buffer;
// Need to mark the allocated range used in the new buffer. How did things work before this?
offset_ = numBytes;
return writePtr_;
}
// For convenience if all you'll do is to copy.
uint32_t Push(const void *data, uint32_t numBytes, int alignment, GLRBuffer **buf) {
uint32_t bindOffset;
uint8_t *ptr = Allocate(numBytes, alignment, buf, &bindOffset);
memcpy(ptr, data, numBytes);
return bindOffset;
}
uint8_t *GetPtr(uint32_t offset) {
return writePtr_ + offset;
}
// If you didn't use all of the previous allocation you just made (obviously can't be another one),
// you can return memory to the buffer by specifying the offset up until which you wrote data.
// Pass in the buffer you got last time. If that buffer has been filled already, no rewind can be safely done.
// (well technically would be possible but not worth the trouble).
void Rewind(GLRBuffer *buffer, uint32_t offset) {
if (buffer == buffers_[buf_].buffer) {
_dbg_assert_(offset != INVALID_OFFSET);
_dbg_assert_(offset <= offset_);
offset_ = offset;
}
}
size_t GetOffset() const { return offset_; }
size_t GetTotalSize() const;
void Destroy(bool onRenderThread);
void Flush();
protected:
void MapDevice(GLBufferStrategy strategy);
void UnmapDevice();
private:
bool AddBuffer();
void NextBuffer(size_t minSize);
void Defragment();
GLRenderManager *render_;
std::vector<BufInfo> buffers_;
size_t buf_ = 0;
size_t offset_ = 0;
size_t size_ = 0;
uint8_t *writePtr_ = nullptr;
GLuint target_;
GLBufferStrategy strategy_ = GLBufferStrategy::SUBDATA;
const char *tag_;
};

View file

@ -118,7 +118,7 @@ static std::string GetStereoBufferLayout(const char *uniformName) {
else return "undefined";
}
void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &steps, bool skipGLCalls) {
void GLQueueRunner::RunInitSteps(const FastVec<GLRInitStep> &steps, bool skipGLCalls) {
if (skipGLCalls) {
// Some bookkeeping still needs to be done.
for (size_t i = 0; i < steps.size(); i++) {
@ -651,7 +651,7 @@ retry_depth:
currentReadHandle_ = fbo->handle;
}
void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCalls, bool keepSteps, bool useVR) {
void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, GLFrameData &frameData, bool skipGLCalls, bool keepSteps, bool useVR) {
if (skipGLCalls) {
if (keepSteps) {
return;
@ -700,7 +700,7 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCal
CHECK_GL_ERROR_IF_DEBUG();
size_t renderCount = 0;
for (size_t i = 0; i < steps.size(); i++) {
const GLRStep &step = *steps[i];
GLRStep &step = *steps[i];
#if !defined(USING_GLES2)
if (useDebugGroups_)
@ -711,11 +711,10 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCal
case GLRStepType::RENDER:
renderCount++;
if (IsVREnabled()) {
GLRStep vrStep = step;
PreprocessStepVR(&vrStep);
PerformRenderPass(vrStep, renderCount == 1, renderCount == totalRenderCount);
PreprocessStepVR(&step);
PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount, frameData.profile);
} else {
PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount);
PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount, frameData.profile);
}
break;
case GLRStepType::COPY:
@ -741,11 +740,14 @@ void GLQueueRunner::RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCal
if (useDebugGroups_)
glPopDebugGroup();
#endif
if (frameData.profile.enabled) {
frameData.profile.passesString += StepToString(step);
}
if (!keepSteps) {
delete steps[i];
}
}
CHECK_GL_ERROR_IF_DEBUG();
}
@ -778,7 +780,20 @@ void GLQueueRunner::PerformBlit(const GLRStep &step) {
}
}
void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last) {
static void EnableDisableVertexArrays(uint32_t prevAttr, uint32_t newAttr) {
int enable = (~prevAttr) & newAttr;
int disable = prevAttr & (~newAttr);
for (int i = 0; i < 7; i++) { // SEM_MAX
if (enable & (1 << i)) {
glEnableVertexAttribArray(i);
}
if (disable & (1 << i)) {
glDisableVertexAttribArray(i);
}
}
}
void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last, GLQueueProfileContext &profile) {
CHECK_GL_ERROR_IF_DEBUG();
PerformBindFramebufferAsRenderTarget(step);
@ -823,12 +838,43 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
bool clipDistanceEnabled[8]{};
GLuint blendEqColor = (GLuint)-1;
GLuint blendEqAlpha = (GLuint)-1;
GLenum blendSrcColor = (GLenum)-1;
GLenum blendDstColor = (GLenum)-1;
GLenum blendSrcAlpha = (GLenum)-1;
GLenum blendDstAlpha = (GLenum)-1;
GLuint stencilWriteMask = (GLuint)-1;
GLenum stencilFunc = (GLenum)-1;
GLuint stencilRef = (GLuint)-1;
GLuint stencilCompareMask = (GLuint)-1;
GLenum stencilSFail = (GLenum)-1;
GLenum stencilZFail = (GLenum)-1;
GLenum stencilPass = (GLenum)-1;
GLenum frontFace = (GLenum)-1;
GLenum cullFace = (GLenum)-1;
GLRTexture *curTex[MAX_GL_TEXTURE_SLOTS]{};
GLRViewport viewport = {
-1000000000.0f,
-1000000000.0f,
-1000000000.0f,
-1000000000.0f,
-1000000000.0f,
-1000000000.0f,
};
GLRect2D scissorRc = { -1, -1, -1, -1 };
CHECK_GL_ERROR_IF_DEBUG();
auto &commands = step.commands;
for (const auto &c : commands) {
#ifdef _DEBUG
if (profile.enabled) {
if ((size_t)c.cmd < ARRAY_SIZE(profile.commandCounts)) {
profile.commandCounts[(size_t)c.cmd]++;
}
}
#endif
switch (c.cmd) {
case GLRRenderCommand::DEPTH:
if (c.depth.enabled) {
@ -849,23 +895,34 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
depthEnabled = false;
}
break;
case GLRRenderCommand::STENCILFUNC:
if (c.stencilFunc.enabled) {
case GLRRenderCommand::STENCIL:
if (c.stencil.enabled) {
if (!stencilEnabled) {
glEnable(GL_STENCIL_TEST);
stencilEnabled = true;
}
glStencilFunc(c.stencilFunc.func, c.stencilFunc.ref, c.stencilFunc.compareMask);
if (c.stencil.func != stencilFunc || c.stencil.ref != stencilRef || c.stencil.compareMask != stencilCompareMask) {
glStencilFunc(c.stencil.func, c.stencil.ref, c.stencil.compareMask);
stencilFunc = c.stencil.func;
stencilRef = c.stencil.ref;
stencilCompareMask = c.stencil.compareMask;
}
if (c.stencil.sFail != stencilSFail || c.stencil.zFail != stencilZFail || c.stencil.pass != stencilPass) {
glStencilOp(c.stencil.sFail, c.stencil.zFail, c.stencil.pass);
stencilSFail = c.stencil.sFail;
stencilZFail = c.stencil.zFail;
stencilPass = c.stencil.pass;
}
if (c.stencil.writeMask != stencilWriteMask) {
glStencilMask(c.stencil.writeMask);
stencilWriteMask = c.stencil.writeMask;
}
} else if (/* !c.stencilFunc.enabled && */stencilEnabled) {
glDisable(GL_STENCIL_TEST);
stencilEnabled = false;
}
CHECK_GL_ERROR_IF_DEBUG();
break;
case GLRRenderCommand::STENCILOP:
glStencilOp(c.stencilOp.sFail, c.stencilOp.zFail, c.stencilOp.pass);
glStencilMask(c.stencilOp.writeMask);
break;
case GLRRenderCommand::BLEND:
if (c.blend.enabled) {
if (!blendEnabled) {
@ -877,7 +934,13 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
blendEqColor = c.blend.funcColor;
blendEqAlpha = c.blend.funcAlpha;
}
if (blendSrcColor != c.blend.srcColor || blendDstColor != c.blend.dstColor || blendSrcAlpha != c.blend.srcAlpha || blendDstAlpha != c.blend.dstAlpha) {
glBlendFuncSeparate(c.blend.srcColor, c.blend.dstColor, c.blend.srcAlpha, c.blend.dstAlpha);
blendSrcColor = c.blend.srcColor;
blendDstColor = c.blend.dstColor;
blendSrcAlpha = c.blend.srcAlpha;
blendDstAlpha = c.blend.dstAlpha;
}
} else if (/* !c.blend.enabled && */ blendEnabled) {
glDisable(GL_BLEND);
blendEnabled = false;
@ -955,7 +1018,17 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
y = curFBHeight_ - y - c.viewport.vp.h;
// TODO: Support FP viewports through glViewportArrays
if (viewport.x != c.viewport.vp.x || viewport.y != y || viewport.w != c.viewport.vp.w || viewport.h != c.viewport.vp.h) {
glViewport((GLint)c.viewport.vp.x, (GLint)y, (GLsizei)c.viewport.vp.w, (GLsizei)c.viewport.vp.h);
viewport.x = c.viewport.vp.x;
viewport.y = y;
viewport.w = c.viewport.vp.w;
viewport.h = c.viewport.vp.h;
}
if (viewport.minZ != c.viewport.vp.minZ || viewport.maxZ != c.viewport.vp.maxZ) {
viewport.minZ = c.viewport.vp.minZ;
viewport.maxZ = c.viewport.vp.maxZ;
#if !defined(USING_GLES2)
if (gl_extensions.IsGLES) {
glDepthRangef(c.viewport.vp.minZ, c.viewport.vp.maxZ);
@ -965,6 +1038,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
#else
glDepthRangef(c.viewport.vp.minZ, c.viewport.vp.maxZ);
#endif
}
CHECK_GL_ERROR_IF_DEBUG();
break;
}
@ -973,7 +1047,13 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
int y = c.scissor.rc.y;
if (!curFB_)
y = curFBHeight_ - y - c.scissor.rc.h;
if (scissorRc.x != c.scissor.rc.x || scissorRc.y != y || scissorRc.w != c.scissor.rc.w || scissorRc.h != c.scissor.rc.h) {
glScissor(c.scissor.rc.x, y, c.scissor.rc.w, c.scissor.rc.h);
scissorRc.x = c.scissor.rc.x;
scissorRc.y = y;
scissorRc.w = c.scissor.rc.w;
scissorRc.h = c.scissor.rc.h;
}
CHECK_GL_ERROR_IF_DEBUG();
break;
}
@ -1038,28 +1118,34 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
{
_dbg_assert_(curProgram);
if (IsMultiviewSupported()) {
int layout = GetStereoBufferIndex(c.uniformMatrix4.name);
int layout = GetStereoBufferIndex(c.uniformStereoMatrix4.name);
if (layout >= 0) {
int size = 2 * 16 * sizeof(float);
glBindBufferBase(GL_UNIFORM_BUFFER, layout, *c.uniformMatrix4.loc);
glBindBuffer(GL_UNIFORM_BUFFER, *c.uniformMatrix4.loc);
glBindBufferBase(GL_UNIFORM_BUFFER, layout, *c.uniformStereoMatrix4.loc);
glBindBuffer(GL_UNIFORM_BUFFER, *c.uniformStereoMatrix4.loc);
void *matrices = glMapBufferRange(GL_UNIFORM_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
memcpy(matrices, c.uniformMatrix4.m, size);
memcpy(matrices, c.uniformStereoMatrix4.mData, size);
glUnmapBuffer(GL_UNIFORM_BUFFER);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
delete[] c.uniformStereoMatrix4.mData; // We only playback once.
} else {
int loc = c.uniformMatrix4.loc ? *c.uniformMatrix4.loc : -1;
if (c.uniformMatrix4.name) {
loc = curProgram->GetUniformLoc(c.uniformMatrix4.name);
int loc = c.uniformStereoMatrix4.loc ? *c.uniformStereoMatrix4.loc : -1;
if (c.uniformStereoMatrix4.name) {
loc = curProgram->GetUniformLoc(c.uniformStereoMatrix4.name);
}
if (loc >= 0) {
if (GetVRFBOIndex() == 0) {
glUniformMatrix4fv(loc, 1, false, c.uniformMatrix4.m);
glUniformMatrix4fv(loc, 1, false, c.uniformStereoMatrix4.mData);
} else {
glUniformMatrix4fv(loc, 1, false, &c.uniformMatrix4.m[16]);
glUniformMatrix4fv(loc, 1, false, c.uniformStereoMatrix4.mData + 16);
}
}
if (GetVRFBOIndex() == 1 || GetVRPassesCount() == 1) {
// Only delete the data if we're rendering the only or the second eye.
// If we delete during the first eye, we get a use-after-free or double delete.
delete[] c.uniformStereoMatrix4.mData;
}
}
CHECK_GL_ERROR_IF_DEBUG();
break;
@ -1148,52 +1234,37 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
CHECK_GL_ERROR_IF_DEBUG();
break;
}
case GLRRenderCommand::BIND_VERTEX_BUFFER:
case GLRRenderCommand::DRAW:
{
// TODO: Add fast path for glBindVertexBuffer
GLRInputLayout *layout = c.bindVertexBuffer.inputLayout;
GLuint buf = c.bindVertexBuffer.buffer ? c.bindVertexBuffer.buffer->buffer_ : 0;
_dbg_assert_(!c.bindVertexBuffer.buffer || !c.bindVertexBuffer.buffer->Mapped());
GLRInputLayout *layout = c.draw.inputLayout;
GLuint buf = c.draw.vertexBuffer->buffer_;
_dbg_assert_(!c.draw.vertexBuffer->Mapped());
if (buf != curArrayBuffer) {
glBindBuffer(GL_ARRAY_BUFFER, buf);
curArrayBuffer = buf;
}
if (attrMask != layout->semanticsMask_) {
int enable = layout->semanticsMask_ & ~attrMask;
int disable = (~layout->semanticsMask_) & attrMask;
for (int i = 0; i < 7; i++) { // SEM_MAX
if (enable & (1 << i)) {
glEnableVertexAttribArray(i);
}
if (disable & (1 << i)) {
glDisableVertexAttribArray(i);
}
}
EnableDisableVertexArrays(attrMask, layout->semanticsMask_);
attrMask = layout->semanticsMask_;
}
for (size_t i = 0; i < layout->entries.size(); i++) {
auto &entry = layout->entries[i];
glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, entry.stride, (const void *)(c.bindVertexBuffer.offset + entry.offset));
glVertexAttribPointer(entry.location, entry.count, entry.type, entry.normalized, entry.stride, (const void *)(c.draw.vertexOffset + entry.offset));
}
CHECK_GL_ERROR_IF_DEBUG();
break;
}
case GLRRenderCommand::BIND_BUFFER:
{
if (c.bind_buffer.target == GL_ARRAY_BUFFER) {
Crash();
} else if (c.bind_buffer.target == GL_ELEMENT_ARRAY_BUFFER) {
GLuint buf = c.bind_buffer.buffer ? c.bind_buffer.buffer->buffer_ : 0;
_dbg_assert_(!(c.bind_buffer.buffer && c.bind_buffer.buffer->Mapped()));
if (c.draw.indexBuffer) {
GLuint buf = c.draw.indexBuffer->buffer_;
_dbg_assert_(!c.draw.indexBuffer->Mapped());
if (buf != curElemArrayBuffer) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf);
curElemArrayBuffer = buf;
}
if (c.draw.instances == 1) {
glDrawElements(c.draw.mode, c.draw.count, c.draw.indexType, (void *)(intptr_t)c.draw.indexOffset);
} else {
GLuint buf = c.bind_buffer.buffer ? c.bind_buffer.buffer->buffer_ : 0;
_dbg_assert_(!(c.bind_buffer.buffer && c.bind_buffer.buffer->Mapped()));
glBindBuffer(c.bind_buffer.target, buf);
glDrawElementsInstanced(c.draw.mode, c.draw.count, c.draw.indexType, (void *)(intptr_t)c.draw.indexOffset, c.draw.instances);
}
} else {
glDrawArrays(c.draw.mode, c.draw.first, c.draw.count);
}
CHECK_GL_ERROR_IF_DEBUG();
break;
@ -1202,16 +1273,6 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
// TODO: Should we include the texture handle in the command?
// Also, should this not be an init command?
glGenerateMipmap(GL_TEXTURE_2D);
break;
case GLRRenderCommand::DRAW:
glDrawArrays(c.draw.mode, c.draw.first, c.draw.count);
break;
case GLRRenderCommand::DRAW_INDEXED:
if (c.drawIndexed.instances == 1) {
glDrawElements(c.drawIndexed.mode, c.drawIndexed.count, c.drawIndexed.indexType, c.drawIndexed.indices);
} else {
glDrawElementsInstanced(c.drawIndexed.mode, c.drawIndexed.count, c.drawIndexed.indexType, c.drawIndexed.indices, c.drawIndexed.instances);
}
CHECK_GL_ERROR_IF_DEBUG();
break;
case GLRRenderCommand::TEXTURESAMPLER:
@ -1315,8 +1376,14 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
glEnable(GL_CULL_FACE);
cullEnabled = true;
}
if (frontFace != c.raster.frontFace) {
glFrontFace(c.raster.frontFace);
frontFace = c.raster.frontFace;
}
if (cullFace != c.raster.cullFace) {
glCullFace(c.raster.cullFace);
cullFace = c.raster.cullFace;
}
} else if (/* !c.raster.cullEnable && */ cullEnabled) {
glDisable(GL_CULL_FACE);
cullEnabled = false;
@ -1784,3 +1851,74 @@ GLRFramebuffer::~GLRFramebuffer() {
glDeleteRenderbuffers(1, &stencil_buffer);
CHECK_GL_ERROR_IF_DEBUG();
}
std::string GLQueueRunner::StepToString(const GLRStep &step) const {
char buffer[256];
switch (step.stepType) {
case GLRStepType::RENDER:
{
int w = step.render.framebuffer ? step.render.framebuffer->width : targetWidth_;
int h = step.render.framebuffer ? step.render.framebuffer->height : targetHeight_;
snprintf(buffer, sizeof(buffer), "RENDER %s %s (commands: %d, %dx%d)\n", step.tag, step.render.framebuffer ? step.render.framebuffer->Tag() : "", (int)step.commands.size(), w, h);
break;
}
case GLRStepType::COPY:
snprintf(buffer, sizeof(buffer), "COPY '%s' %s -> %s (%dx%d, %s)\n", step.tag, step.copy.src->Tag(), step.copy.dst->Tag(), step.copy.srcRect.w, step.copy.srcRect.h, GLRAspectToString((GLRAspect)step.copy.aspectMask));
break;
case GLRStepType::BLIT:
snprintf(buffer, sizeof(buffer), "BLIT '%s' %s -> %s (%dx%d->%dx%d, %s)\n", step.tag, step.copy.src->Tag(), step.copy.dst->Tag(), step.blit.srcRect.w, step.blit.srcRect.h, step.blit.dstRect.w, step.blit.dstRect.h, GLRAspectToString((GLRAspect)step.blit.aspectMask));
break;
case GLRStepType::READBACK:
snprintf(buffer, sizeof(buffer), "READBACK '%s' %s (%dx%d, %s)\n", step.tag, step.readback.src ? step.readback.src->Tag() : "(backbuffer)", step.readback.srcRect.w, step.readback.srcRect.h, GLRAspectToString((GLRAspect)step.readback.aspectMask));
break;
case GLRStepType::READBACK_IMAGE:
snprintf(buffer, sizeof(buffer), "READBACK_IMAGE '%s' (%dx%d)\n", step.tag, step.readback_image.srcRect.w, step.readback_image.srcRect.h);
break;
case GLRStepType::RENDER_SKIP:
snprintf(buffer, sizeof(buffer), "(RENDER_SKIP) %s\n", step.tag);
break;
default:
buffer[0] = 0;
break;
}
return std::string(buffer);
}
const char *GLRAspectToString(GLRAspect aspect) {
switch (aspect) {
case GLR_ASPECT_COLOR: return "COLOR";
case GLR_ASPECT_DEPTH: return "DEPTH";
case GLR_ASPECT_STENCIL: return "STENCIL";
default: return "N/A";
}
}
const char *RenderCommandToString(GLRRenderCommand cmd) {
switch (cmd) {
case GLRRenderCommand::DEPTH: return "DEPTH";
case GLRRenderCommand::STENCIL: return "STENCIL";
case GLRRenderCommand::BLEND: return "BLEND";
case GLRRenderCommand::BLENDCOLOR: return "BLENDCOLOR";
case GLRRenderCommand::LOGICOP: return "LOGICOP";
case GLRRenderCommand::UNIFORM4I: return "UNIFORM4I";
case GLRRenderCommand::UNIFORM4UI: return "UNIFORM4UI";
case GLRRenderCommand::UNIFORM4F: return "UNIFORM4F";
case GLRRenderCommand::UNIFORMMATRIX: return "UNIFORMMATRIX";
case GLRRenderCommand::UNIFORMSTEREOMATRIX: return "UNIFORMSTEREOMATRIX";
case GLRRenderCommand::TEXTURESAMPLER: return "TEXTURESAMPLER";
case GLRRenderCommand::TEXTURELOD: return "TEXTURELOD";
case GLRRenderCommand::VIEWPORT: return "VIEWPORT";
case GLRRenderCommand::SCISSOR: return "SCISSOR";
case GLRRenderCommand::RASTER: return "RASTER";
case GLRRenderCommand::CLEAR: return "CLEAR";
case GLRRenderCommand::INVALIDATE: return "INVALIDATE";
case GLRRenderCommand::BINDPROGRAM: return "BINDPROGRAM";
case GLRRenderCommand::BINDTEXTURE: return "BINDTEXTURE";
case GLRRenderCommand::BIND_FB_TEXTURE: return "BIND_FB_TEXTURE";
case GLRRenderCommand::BIND_VERTEX_BUFFER: return "BIND_VERTEX_BUFFER";
case GLRRenderCommand::GENMIPS: return "GENMIPS";
case GLRRenderCommand::DRAW: return "DRAW";
case GLRRenderCommand::TEXTURE_SUBIMAGE: return "TEXTURE_SUBIMAGE";
default: return "N/A";
}
}

View file

@ -11,7 +11,7 @@
#include "Common/GPU/Shader.h"
#include "Common/GPU/thin3d.h"
#include "Common/Data/Collections/TinySet.h"
#include "Common/Data/Collections/FastVec.h"
struct GLRViewport {
float x, y, w, h, minZ, maxZ;
@ -25,7 +25,7 @@ struct GLOffset2D {
int x, y;
};
enum class GLRAllocType {
enum class GLRAllocType : uint8_t {
NONE,
NEW,
ALIGNED,
@ -40,8 +40,7 @@ class GLRInputLayout;
enum class GLRRenderCommand : uint8_t {
DEPTH,
STENCILFUNC,
STENCILOP,
STENCIL,
BLEND,
BLENDCOLOR,
LOGICOP,
@ -61,10 +60,8 @@ enum class GLRRenderCommand : uint8_t {
BINDTEXTURE,
BIND_FB_TEXTURE,
BIND_VERTEX_BUFFER,
BIND_BUFFER,
GENMIPS,
DRAW,
DRAW_INDEXED,
TEXTURE_SUBIMAGE,
};
@ -72,6 +69,7 @@ enum class GLRRenderCommand : uint8_t {
// type field, smashed right after each other?)
// Also, all GLenums are really only 16 bits.
struct GLRRenderData {
GLRRenderData(GLRRenderCommand _cmd) : cmd(_cmd) {}
GLRRenderCommand cmd;
union {
struct {
@ -101,26 +99,23 @@ struct GLRRenderData {
GLenum func;
uint8_t ref;
uint8_t compareMask;
} stencilFunc;
struct {
GLenum sFail;
GLenum zFail;
GLenum pass;
uint8_t writeMask;
} stencilOp; // also write mask
} stencil;
struct {
GLRInputLayout *inputLayout;
GLRBuffer *vertexBuffer;
GLRBuffer *indexBuffer;
uint32_t vertexOffset;
uint32_t indexOffset;
GLenum mode; // primitive
GLint buffer;
GLint first;
GLint count;
} draw;
struct {
GLenum mode; // primitive
GLint count;
GLint instances;
GLint indexType;
void *indices;
} drawIndexed;
GLint instances;
} draw;
struct {
const char *name; // if null, use loc
const GLint *loc; // NOTE: This is a pointer so we can immediately use things that are "queried" during program creation.
@ -130,8 +125,13 @@ struct GLRRenderData {
struct {
const char *name; // if null, use loc
const GLint *loc;
float m[32];
float m[16];
} uniformMatrix4;
struct {
const char *name; // if null, use loc
const GLint *loc;
float *mData; // new'd, 32 entries
} uniformStereoMatrix4;
struct {
uint32_t clearColor;
float clearZ;
@ -171,11 +171,6 @@ struct GLRRenderData {
struct {
GLRProgram *program;
} program;
struct {
GLRInputLayout *inputLayout;
GLRBuffer *buffer;
size_t offset;
} bindVertexBuffer;
struct {
int slot;
GLenum wrapS;
@ -295,16 +290,17 @@ enum class GLRRenderPassAction {
class GLRFramebuffer;
enum {
enum GLRAspect {
GLR_ASPECT_COLOR = 1,
GLR_ASPECT_DEPTH = 2,
GLR_ASPECT_STENCIL = 3,
};
const char *GLRAspectToString(GLRAspect aspect);
struct GLRStep {
GLRStep(GLRStepType _type) : stepType(_type) {}
GLRStepType stepType;
std::vector<GLRRenderData> commands;
FastVec<GLRRenderData> commands;
TinySet<const GLRFramebuffer *, 8> dependencies;
const char *tag;
union {
@ -313,8 +309,6 @@ struct GLRStep {
GLRRenderPassAction color;
GLRRenderPassAction depth;
GLRRenderPassAction stencil;
// Note: not accurate.
int numDraws;
} render;
struct {
GLRFramebuffer *src;
@ -358,9 +352,9 @@ public:
caps_ = caps;
}
void RunInitSteps(const std::vector<GLRInitStep> &steps, bool skipGLCalls);
void RunInitSteps(const FastVec<GLRInitStep> &steps, bool skipGLCalls);
void RunSteps(const std::vector<GLRStep *> &steps, bool skipGLCalls, bool keepSteps, bool useVR);
void RunSteps(const std::vector<GLRStep *> &steps, GLFrameData &frameData, bool skipGLCalls, bool keepSteps, bool useVR);
void CreateDeviceObjects();
void DestroyDeviceObjects();
@ -385,7 +379,7 @@ private:
void InitCreateFramebuffer(const GLRInitStep &step);
void PerformBindFramebufferAsRenderTarget(const GLRStep &pass);
void PerformRenderPass(const GLRStep &pass, bool first, bool last);
void PerformRenderPass(const GLRStep &pass, bool first, bool last, GLQueueProfileContext &profile);
void PerformCopy(const GLRStep &pass);
void PerformBlit(const GLRStep &pass);
void PerformReadback(const GLRStep &pass);
@ -396,6 +390,8 @@ private:
GLenum fbo_get_fb_target(bool read, GLuint **cached);
void fbo_unbind();
std::string StepToString(const GLRStep &step) const;
GLRFramebuffer *curFB_ = nullptr;
GLuint globalVAO_ = 0;
@ -427,3 +423,5 @@ private:
ErrorCallbackFn errorCallback_ = nullptr;
void *errorCallbackUserData_ = nullptr;
};
const char *RenderCommandToString(GLRRenderCommand cmd);

View file

@ -8,6 +8,7 @@
#include "Common/Log.h"
#include "Common/TimeUtil.h"
#include "Common/MemoryUtil.h"
#include "Common/StringUtils.h"
#include "Common/Math/math_util.h"
#if 0 // def _DEBUG
@ -16,12 +17,7 @@
#define VLOG(...)
#endif
static std::thread::id renderThreadId;
#if MAX_LOGLEVEL >= DEBUG_LEVEL
static bool OnRenderThread() {
return std::this_thread::get_id() == renderThreadId;
}
#endif
std::thread::id renderThreadId;
GLRTexture::GLRTexture(const Draw::DeviceCaps &caps, int width, int height, int depth, int numMips) {
if (caps.textureNPOTFullySupported) {
@ -41,6 +37,11 @@ GLRTexture::~GLRTexture() {
}
}
GLRenderManager::GLRenderManager() {
// size_t sz = sizeof(GLRRenderData);
// _dbg_assert_(sz == 88);
}
GLRenderManager::~GLRenderManager() {
_dbg_assert_(!run_);
@ -128,25 +129,24 @@ bool GLRenderManager::ThreadFrame() {
return false;
}
GLRRenderThreadTask task;
GLRRenderThreadTask *task = nullptr;
// In case of syncs or other partial completion, we keep going until we complete a frame.
while (true) {
// Pop a task of the queue and execute it.
// NOTE: We need to actually wait for a task, we can't just bail!
{
std::unique_lock<std::mutex> lock(pushMutex_);
while (renderThreadQueue_.empty()) {
pushCondVar_.wait(lock);
}
task = renderThreadQueue_.front();
task = std::move(renderThreadQueue_.front());
renderThreadQueue_.pop();
}
// We got a task! We can now have pushMutex_ unlocked, allowing the host to
// push more work when it feels like it, and just start working.
if (task.runType == GLRRunType::EXIT) {
if (task->runType == GLRRunType::EXIT) {
// Oh, host wanted out. Let's leave, and also let's notify the host.
// This is unlike Vulkan too which can just block on the thread existing.
std::unique_lock<std::mutex> lock(syncMutex_);
@ -156,11 +156,13 @@ bool GLRenderManager::ThreadFrame() {
}
// Render the scene.
VLOG(" PULL: Frame %d RUN (%0.3f)", task.frame, time_now_d());
if (Run(task)) {
VLOG(" PULL: Frame %d RUN (%0.3f)", task->frame, time_now_d());
if (Run(*task)) {
// Swap requested, so we just bail the loop.
delete task;
break;
}
delete task;
};
return true;
@ -173,15 +175,21 @@ void GLRenderManager::StopThread() {
run_ = false;
std::unique_lock<std::mutex> lock(pushMutex_);
GLRRenderThreadTask exitTask{};
exitTask.runType = GLRRunType::EXIT;
renderThreadQueue_.push(exitTask);
renderThreadQueue_.push(new GLRRenderThreadTask(GLRRunType::EXIT));
pushCondVar_.notify_one();
} else {
WARN_LOG(G3D, "GL submission thread was already paused.");
}
}
std::string GLRenderManager::GetGpuProfileString() const {
int curFrame = GetCurFrame();
const GLQueueProfileContext &profile = frameData_[curFrame].profile;
float cputime_ms = 1000.0f * (profile.cpuEndTime - profile.cpuStartTime);
return StringFromFormat("CPU time to run the list: %0.2f ms\n\n%s", cputime_ms, profilePassesString_.c_str());
}
void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRenderPassAction color, GLRRenderPassAction depth, GLRRenderPassAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) {
_assert_(insideFrame_);
#ifdef _DEBUG
@ -206,13 +214,11 @@ void GLRenderManager::BindFramebufferAsRenderTarget(GLRFramebuffer *fb, GLRRende
step->render.color = color;
step->render.depth = depth;
step->render.stencil = stencil;
step->render.numDraws = 0;
step->tag = tag;
steps_.push_back(step);
GLuint clearMask = 0;
GLRRenderData data;
data.cmd = GLRRenderCommand::CLEAR;
GLRRenderData data(GLRRenderCommand::CLEAR);
if (color == GLRRenderPassAction::CLEAR) {
clearMask |= GL_COLOR_BUFFER_BIT;
data.clear.clearColor = clearColor;
@ -336,7 +342,7 @@ void GLRenderManager::CopyImageToMemorySync(GLRTexture *texture, int mipLevel, i
queueRunner_.CopyFromReadbackBuffer(nullptr, w, h, Draw::DataFormat::R8G8B8A8_UNORM, destFormat, pixelStride, pixels);
}
void GLRenderManager::BeginFrame() {
void GLRenderManager::BeginFrame(bool enableProfiling) {
#ifdef _DEBUG
curProgram_ = nullptr;
#endif
@ -344,6 +350,8 @@ void GLRenderManager::BeginFrame() {
int curFrame = GetCurFrame();
GLFrameData &frameData = frameData_[curFrame];
frameData.profile.enabled = enableProfiling;
{
VLOG("PUSH: BeginFrame (curFrame = %d, readyForFence = %d, time=%0.3f)", curFrame, (int)frameData.readyForFence, time_now_d());
std::unique_lock<std::mutex> lock(frameData.fenceMutex);
@ -369,20 +377,36 @@ void GLRenderManager::Finish() {
frameData_[curFrame].deleter.Take(deleter_);
VLOG("PUSH: Finish, pushing task. curFrame = %d", curFrame);
GLRRenderThreadTask task;
task.frame = curFrame;
task.runType = GLRRunType::PRESENT;
GLRRenderThreadTask *task = new GLRRenderThreadTask(GLRRunType::PRESENT);
task->frame = curFrame;
{
std::unique_lock<std::mutex> lock(pushMutex_);
renderThreadQueue_.push(task);
renderThreadQueue_.back().initSteps = std::move(initSteps_);
renderThreadQueue_.back().steps = std::move(steps_);
renderThreadQueue_.back()->initSteps = std::move(initSteps_);
renderThreadQueue_.back()->steps = std::move(steps_);
initSteps_.clear();
steps_.clear();
pushCondVar_.notify_one();
}
if (frameData.profile.enabled) {
profilePassesString_ = std::move(frameData.profile.passesString);
#ifdef _DEBUG
std::string cmdString;
for (int i = 0; i < ARRAY_SIZE(frameData.profile.commandCounts); i++) {
if (frameData.profile.commandCounts[i] > 0) {
cmdString += StringFromFormat("%s: %d\n", RenderCommandToString((GLRRenderCommand)i), frameData.profile.commandCounts[i]);
}
}
memset(frameData.profile.commandCounts, 0, sizeof(frameData.profile.commandCounts));
profilePassesString_ = cmdString + profilePassesString_;
#endif
frameData.profile.passesString.clear();
}
curFrame_++;
if (curFrame_ >= inflightFrames_)
curFrame_ = 0;
@ -412,15 +436,23 @@ bool GLRenderManager::Run(GLRRenderThreadTask &task) {
}
}
if (frameData.profile.enabled) {
frameData.profile.cpuStartTime = time_now_d();
}
if (IsVREnabled()) {
int passes = GetVRPassesCount();
for (int i = 0; i < passes; i++) {
PreVRFrameRender(i);
queueRunner_.RunSteps(task.steps, skipGLCalls_, i < passes - 1, true);
queueRunner_.RunSteps(task.steps, frameData, skipGLCalls_, i < passes - 1, true);
PostVRFrameRender();
}
} else {
queueRunner_.RunSteps(task.steps, skipGLCalls_, false, false);
queueRunner_.RunSteps(task.steps, frameData, skipGLCalls_, false, false);
}
if (frameData.profile.enabled) {
frameData.profile.cpuEndTime = time_now_d();
}
if (!skipGLCalls_) {
@ -491,14 +523,13 @@ void GLRenderManager::FlushSync() {
{
VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame_);
GLRRenderThreadTask task;
task.frame = curFrame_;
task.runType = GLRRunType::SYNC;
GLRRenderThreadTask *task = new GLRRenderThreadTask(GLRRunType::SYNC);
task->frame = curFrame_;
std::unique_lock<std::mutex> lock(pushMutex_);
renderThreadQueue_.push(task);
renderThreadQueue_.back().initSteps = std::move(initSteps_);
renderThreadQueue_.back().steps = std::move(steps_);
renderThreadQueue_.back()->initSteps = std::move(initSteps_);
renderThreadQueue_.back()->steps = std::move(steps_);
pushCondVar_.notify_one();
steps_.clear();
}
@ -513,254 +544,3 @@ void GLRenderManager::FlushSync() {
syncDone_ = false;
}
}
GLPushBuffer::GLPushBuffer(GLRenderManager *render, GLuint target, size_t size) : render_(render), size_(size), target_(target) {
bool res = AddBuffer();
_assert_(res);
}
GLPushBuffer::~GLPushBuffer() {
Destroy(true);
}
void GLPushBuffer::Map() {
_assert_(!writePtr_);
auto &info = buffers_[buf_];
writePtr_ = info.deviceMemory ? info.deviceMemory : info.localMemory;
info.flushOffset = 0;
// Force alignment. This is needed for PushAligned() to work as expected.
while ((intptr_t)writePtr_ & 15) {
writePtr_++;
offset_++;
info.flushOffset++;
}
_assert_(writePtr_);
}
void GLPushBuffer::Unmap() {
_assert_(writePtr_);
if (!buffers_[buf_].deviceMemory) {
// Here we simply upload the data to the last buffer.
// Might be worth trying with size_ instead of offset_, so the driver can replace
// the whole buffer. At least if it's close.
render_->BufferSubdata(buffers_[buf_].buffer, 0, offset_, buffers_[buf_].localMemory, false);
} else {
buffers_[buf_].flushOffset = offset_;
}
writePtr_ = nullptr;
}
void GLPushBuffer::Flush() {
// Must be called from the render thread.
_dbg_assert_(OnRenderThread());
buffers_[buf_].flushOffset = offset_;
if (!buffers_[buf_].deviceMemory && writePtr_) {
auto &info = buffers_[buf_];
if (info.flushOffset != 0) {
_assert_(info.buffer->buffer_);
glBindBuffer(target_, info.buffer->buffer_);
glBufferSubData(target_, 0, info.flushOffset, info.localMemory);
}
// Here we will submit all the draw calls, with the already known buffer and offsets.
// Might as well reset the write pointer here and start over the current buffer.
writePtr_ = info.localMemory;
offset_ = 0;
info.flushOffset = 0;
}
// For device memory, we flush all buffers here.
if ((strategy_ & GLBufferStrategy::MASK_FLUSH) != 0) {
for (auto &info : buffers_) {
if (info.flushOffset == 0 || !info.deviceMemory)
continue;
glBindBuffer(target_, info.buffer->buffer_);
glFlushMappedBufferRange(target_, 0, info.flushOffset);
info.flushOffset = 0;
}
}
}
bool GLPushBuffer::AddBuffer() {
BufInfo info;
info.localMemory = (uint8_t *)AllocateAlignedMemory(size_, 16);
if (!info.localMemory)
return false;
info.buffer = render_->CreateBuffer(target_, size_, GL_DYNAMIC_DRAW);
buf_ = buffers_.size();
buffers_.push_back(info);
return true;
}
void GLPushBuffer::Destroy(bool onRenderThread) {
if (buf_ == -1)
return; // Already destroyed
for (BufInfo &info : buffers_) {
// This will automatically unmap device memory, if needed.
// NOTE: We immediately delete the buffer, don't go through the deleter, if we're on the render thread.
if (onRenderThread) {
delete info.buffer;
} else {
render_->DeleteBuffer(info.buffer);
}
FreeAlignedMemory(info.localMemory);
}
buffers_.clear();
buf_ = -1;
}
void GLPushBuffer::NextBuffer(size_t minSize) {
// First, unmap the current memory.
Unmap();
buf_++;
if (buf_ >= buffers_.size() || minSize > size_) {
// Before creating the buffer, adjust to the new size_ if necessary.
while (size_ < minSize) {
size_ <<= 1;
}
bool res = AddBuffer();
_assert_(res);
if (!res) {
// Let's try not to crash at least?
buf_ = 0;
}
}
// Now, move to the next buffer and map it.
offset_ = 0;
Map();
}
void GLPushBuffer::Defragment() {
_dbg_assert_msg_(!OnRenderThread(), "Defragment must not run on the render thread");
if (buffers_.size() <= 1) {
// Let's take this chance to jetison localMemory we don't need.
for (auto &info : buffers_) {
if (info.deviceMemory) {
FreeAlignedMemory(info.localMemory);
info.localMemory = nullptr;
}
}
return;
}
// Okay, we have more than one. Destroy them all and start over with a larger one.
size_t newSize = size_ * buffers_.size();
Destroy(false);
size_ = newSize;
bool res = AddBuffer();
_assert_msg_(res, "AddBuffer failed");
}
size_t GLPushBuffer::GetTotalSize() const {
size_t sum = 0;
if (buffers_.size() > 1)
sum += size_ * (buffers_.size() - 1);
sum += offset_;
return sum;
}
void GLPushBuffer::MapDevice(GLBufferStrategy strategy) {
_dbg_assert_msg_(OnRenderThread(), "MapDevice must run on render thread");
strategy_ = strategy;
if (strategy_ == GLBufferStrategy::SUBDATA) {
return;
}
bool mapChanged = false;
for (auto &info : buffers_) {
if (!info.buffer->buffer_ || info.deviceMemory) {
// Can't map - no device buffer associated yet or already mapped.
continue;
}
info.deviceMemory = (uint8_t *)info.buffer->Map(strategy_);
mapChanged = mapChanged || info.deviceMemory != nullptr;
if (!info.deviceMemory && !info.localMemory) {
// Somehow it failed, let's dodge crashing.
info.localMemory = (uint8_t *)AllocateAlignedMemory(info.buffer->size_, 16);
mapChanged = true;
}
_dbg_assert_msg_(info.localMemory || info.deviceMemory, "Local or device memory must succeed");
}
if (writePtr_ && mapChanged) {
// This can happen during a sync. Remap.
writePtr_ = nullptr;
Map();
}
}
void GLPushBuffer::UnmapDevice() {
_dbg_assert_msg_(OnRenderThread(), "UnmapDevice must run on render thread");
for (auto &info : buffers_) {
if (info.deviceMemory) {
// TODO: Technically this can return false?
info.buffer->Unmap();
info.deviceMemory = nullptr;
}
}
}
void *GLRBuffer::Map(GLBufferStrategy strategy) {
_assert_(buffer_ != 0);
GLbitfield access = GL_MAP_WRITE_BIT;
if ((strategy & GLBufferStrategy::MASK_FLUSH) != 0) {
access |= GL_MAP_FLUSH_EXPLICIT_BIT;
}
if ((strategy & GLBufferStrategy::MASK_INVALIDATE) != 0) {
access |= GL_MAP_INVALIDATE_BUFFER_BIT;
}
void *p = nullptr;
bool allowNativeBuffer = strategy != GLBufferStrategy::SUBDATA;
if (allowNativeBuffer) {
glBindBuffer(target_, buffer_);
if (gl_extensions.ARB_buffer_storage || gl_extensions.EXT_buffer_storage) {
#if !PPSSPP_PLATFORM(IOS)
if (!hasStorage_) {
GLbitfield storageFlags = access & ~(GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
#ifdef USING_GLES2
#ifdef GL_EXT_buffer_storage
glBufferStorageEXT(target_, size_, nullptr, storageFlags);
#endif
#else
glBufferStorage(target_, size_, nullptr, storageFlags);
#endif
hasStorage_ = true;
}
#endif
p = glMapBufferRange(target_, 0, size_, access);
} else if (gl_extensions.VersionGEThan(3, 0, 0)) {
// GLES3 or desktop 3.
p = glMapBufferRange(target_, 0, size_, access);
} else if (!gl_extensions.IsGLES) {
#ifndef USING_GLES2
p = glMapBuffer(target_, GL_READ_WRITE);
#endif
}
}
mapped_ = p != nullptr;
return p;
}
bool GLRBuffer::Unmap() {
glBindBuffer(target_, buffer_);
mapped_ = false;
return glUnmapBuffer(target_) == GL_TRUE;
}

View file

@ -16,6 +16,7 @@
#include "Common/GPU/OpenGL/GLQueueRunner.h"
#include "Common/GPU/OpenGL/GLFrameData.h"
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/GPU/OpenGL/GLMemory.h"
class GLRInputLayout;
class GLPushBuffer;
@ -52,13 +53,14 @@ public:
class GLRFramebuffer {
public:
GLRFramebuffer(const Draw::DeviceCaps &caps, int _width, int _height, bool z_stencil)
GLRFramebuffer(const Draw::DeviceCaps &caps, int _width, int _height, bool z_stencil, const char *tag)
: color_texture(caps, _width, _height, 1, 1), z_stencil_texture(caps, _width, _height, 1, 1),
width(_width), height(_height), z_stencil_(z_stencil) {
}
~GLRFramebuffer();
const char *Tag() const { return tag_.c_str(); }
GLuint handle = 0;
GLRTexture color_texture;
// Either z_stencil_texture, z_stencil_buffer, or (z_buffer and stencil_buffer) are set.
@ -70,8 +72,10 @@ public:
int width;
int height;
GLuint colorDepth = 0;
bool z_stencil_;
private:
std::string tag_;
};
// We need to create some custom heap-allocated types so we can forward things that need to be created on the GL thread, before
@ -179,178 +183,6 @@ private:
std::unordered_map<std::string, UniformInfo> uniformCache_;
};
enum class GLBufferStrategy {
SUBDATA = 0,
MASK_FLUSH = 0x10,
MASK_INVALIDATE = 0x20,
// Map/unmap the buffer each frame.
FRAME_UNMAP = 1,
// Map/unmap and also invalidate the buffer on map.
INVALIDATE_UNMAP = MASK_INVALIDATE,
// Map/unmap and explicitly flushed changed ranges.
FLUSH_UNMAP = MASK_FLUSH,
// Map/unmap, invalidate on map, and explicit flush.
FLUSH_INVALIDATE_UNMAP = MASK_FLUSH | MASK_INVALIDATE,
};
static inline int operator &(const GLBufferStrategy &lhs, const GLBufferStrategy &rhs) {
return (int)lhs & (int)rhs;
}
class GLRBuffer {
public:
GLRBuffer(GLuint target, size_t size) : target_(target), size_((int)size) {}
~GLRBuffer() {
if (buffer_) {
glDeleteBuffers(1, &buffer_);
}
}
void *Map(GLBufferStrategy strategy);
bool Unmap();
bool Mapped() const {
return mapped_;
}
GLuint buffer_ = 0;
GLuint target_;
int size_;
private:
bool mapped_ = false;
bool hasStorage_ = false;
};
class GLRenderManager;
// Similar to VulkanPushBuffer but is currently less efficient - it collects all the data in
// RAM then does a big memcpy/buffer upload at the end of the frame. This is at least a lot
// faster than the hundreds of buffer uploads or memory array buffers we used before.
// On modern GL we could avoid the copy using glBufferStorage but not sure it's worth the
// trouble.
// We need to manage the lifetime of this together with the other resources so its destructor
// runs on the render thread.
class GLPushBuffer {
public:
friend class GLRenderManager;
struct BufInfo {
GLRBuffer *buffer = nullptr;
uint8_t *localMemory = nullptr;
uint8_t *deviceMemory = nullptr;
size_t flushOffset = 0;
};
GLPushBuffer(GLRenderManager *render, GLuint target, size_t size);
~GLPushBuffer();
void Reset() { offset_ = 0; }
private:
// Needs context in case of defragment.
void Begin() {
buf_ = 0;
offset_ = 0;
// Note: we must defrag because some buffers may be smaller than size_.
Defragment();
Map();
_dbg_assert_(writePtr_);
}
void BeginNoReset() {
Map();
}
void End() {
Unmap();
}
public:
void Map();
void Unmap();
bool IsReady() const {
return writePtr_ != nullptr;
}
// When using the returned memory, make sure to bind the returned vkbuf.
// This will later allow for handling overflow correctly.
size_t Allocate(size_t numBytes, GLRBuffer **vkbuf) {
size_t out = offset_;
if (offset_ + ((numBytes + 3) & ~3) >= size_) {
NextBuffer(numBytes);
out = offset_;
offset_ += (numBytes + 3) & ~3;
} else {
offset_ += (numBytes + 3) & ~3; // Round up to 4 bytes.
}
*vkbuf = buffers_[buf_].buffer;
return out;
}
// Returns the offset that should be used when binding this buffer to get this data.
size_t Push(const void *data, size_t size, GLRBuffer **vkbuf) {
_dbg_assert_(writePtr_);
size_t off = Allocate(size, vkbuf);
memcpy(writePtr_ + off, data, size);
return off;
}
uint32_t PushAligned(const void *data, size_t size, int align, GLRBuffer **vkbuf) {
_dbg_assert_(writePtr_);
offset_ = (offset_ + align - 1) & ~(align - 1);
size_t off = Allocate(size, vkbuf);
memcpy(writePtr_ + off, data, size);
return (uint32_t)off;
}
size_t GetOffset() const {
return offset_;
}
// "Zero-copy" variant - you can write the data directly as you compute it.
// Recommended.
void *Push(size_t size, uint32_t *bindOffset, GLRBuffer **vkbuf) {
_dbg_assert_(writePtr_);
size_t off = Allocate(size, vkbuf);
*bindOffset = (uint32_t)off;
return writePtr_ + off;
}
void *PushAligned(size_t size, uint32_t *bindOffset, GLRBuffer **vkbuf, int align) {
_dbg_assert_(writePtr_);
offset_ = (offset_ + align - 1) & ~(align - 1);
size_t off = Allocate(size, vkbuf);
*bindOffset = (uint32_t)off;
return writePtr_ + off;
}
size_t GetTotalSize() const;
void Destroy(bool onRenderThread);
void Flush();
protected:
void MapDevice(GLBufferStrategy strategy);
void UnmapDevice();
private:
bool AddBuffer();
void NextBuffer(size_t minSize);
void Defragment();
GLRenderManager *render_;
std::vector<BufInfo> buffers_;
size_t buf_ = 0;
size_t offset_ = 0;
size_t size_ = 0;
uint8_t *writePtr_ = nullptr;
GLuint target_;
GLBufferStrategy strategy_ = GLBufferStrategy::SUBDATA;
};
class GLRInputLayout {
public:
struct Entry {
@ -374,14 +206,19 @@ enum class GLRRunType {
class GLRenderManager;
class GLPushBuffer;
// These are enqueued from the main thread,
// and the render thread pops them off
// These are enqueued from the main thread, and the render thread pops them off
struct GLRRenderThreadTask {
std::vector<GLRStep *> steps;
std::vector<GLRInitStep> initSteps;
GLRRenderThreadTask(GLRRunType _runType) : runType(_runType) {}
int frame;
std::vector<GLRStep *> steps;
FastVec<GLRInitStep> initSteps;
int frame = -1;
GLRRunType runType;
// Avoid copying these by accident.
GLRRenderThreadTask(GLRRenderThreadTask &) = delete;
GLRRenderThreadTask& operator =(GLRRenderThreadTask &) = delete;
};
// Note: The GLRenderManager is created and destroyed on the render thread, and the latter
@ -389,9 +226,11 @@ struct GLRRenderThreadTask {
// directly in the destructor.
class GLRenderManager {
public:
GLRenderManager() {}
GLRenderManager();
~GLRenderManager();
GLRenderManager(GLRenderManager &) = delete;
GLRenderManager &operator=(GLRenderManager &) = delete;
void SetInvalidationCallback(InvalidationCallback callback) {
invalidationCallback_ = callback;
@ -409,8 +248,10 @@ public:
caps_ = caps;
}
std::string GetGpuProfileString() const;
// Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again.
void BeginFrame();
void BeginFrame(bool enableProfiling);
// Can run on a different thread!
void Finish();
@ -418,37 +259,40 @@ public:
// We pass in width/height here even though it's not strictly needed until we support glTextureStorage
// and then we'll also need formats and stuff.
GLRTexture *CreateTexture(GLenum target, int width, int height, int depth, int numMips) {
GLRInitStep step { GLRInitStepType::CREATE_TEXTURE };
_dbg_assert_(target != 0);
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_TEXTURE;
step.create_texture.texture = new GLRTexture(caps_, width, height, depth, numMips);
step.create_texture.texture->target = target;
initSteps_.push_back(step);
return step.create_texture.texture;
}
GLRBuffer *CreateBuffer(GLuint target, size_t size, GLuint usage) {
GLRInitStep step{ GLRInitStepType::CREATE_BUFFER };
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_BUFFER;
step.create_buffer.buffer = new GLRBuffer(target, size);
step.create_buffer.size = (int)size;
step.create_buffer.usage = usage;
initSteps_.push_back(step);
return step.create_buffer.buffer;
}
GLRShader *CreateShader(GLuint stage, const std::string &code, const std::string &desc) {
GLRInitStep step{ GLRInitStepType::CREATE_SHADER };
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_SHADER;
step.create_shader.shader = new GLRShader();
step.create_shader.shader->desc = desc;
step.create_shader.stage = stage;
step.create_shader.code = new char[code.size() + 1];
memcpy(step.create_shader.code, code.data(), code.size() + 1);
initSteps_.push_back(step);
return step.create_shader.shader;
}
GLRFramebuffer *CreateFramebuffer(int width, int height, bool z_stencil) {
GLRInitStep step{ GLRInitStepType::CREATE_FRAMEBUFFER };
step.create_framebuffer.framebuffer = new GLRFramebuffer(caps_, width, height, z_stencil);
initSteps_.push_back(step);
GLRFramebuffer *CreateFramebuffer(int width, int height, bool z_stencil, const char *tag) {
_dbg_assert_(width > 0 && height > 0 && tag != nullptr);
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_FRAMEBUFFER;
step.create_framebuffer.framebuffer = new GLRFramebuffer(caps_, width, height, z_stencil, tag);
return step.create_framebuffer.framebuffer;
}
@ -457,7 +301,8 @@ public:
GLRProgram *CreateProgram(
std::vector<GLRShader *> shaders, std::vector<GLRProgram::Semantic> semantics, std::vector<GLRProgram::UniformLocQuery> queries,
std::vector<GLRProgram::Initializer> initializers, GLRProgramLocData *locData, const GLRProgramFlags &flags) {
GLRInitStep step{ GLRInitStepType::CREATE_PROGRAM };
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_PROGRAM;
_assert_(shaders.size() <= ARRAY_SIZE(step.create_program.shaders));
step.create_program.program = new GLRProgram();
step.create_program.program->semantics_ = semantics;
@ -481,23 +326,22 @@ public:
}
#endif
step.create_program.num_shaders = (int)shaders.size();
initSteps_.push_back(step);
return step.create_program.program;
}
GLRInputLayout *CreateInputLayout(const std::vector<GLRInputLayout::Entry> &entries) {
GLRInitStep step{ GLRInitStepType::CREATE_INPUT_LAYOUT };
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::CREATE_INPUT_LAYOUT;
step.create_input_layout.inputLayout = new GLRInputLayout();
step.create_input_layout.inputLayout->entries = entries;
for (auto &iter : step.create_input_layout.inputLayout->entries) {
step.create_input_layout.inputLayout->semanticsMask_ |= 1 << iter.location;
}
initSteps_.push_back(step);
return step.create_input_layout.inputLayout;
}
GLPushBuffer *CreatePushBuffer(int frame, GLuint target, size_t size) {
GLPushBuffer *push = new GLPushBuffer(this, target, size);
GLPushBuffer *CreatePushBuffer(int frame, GLuint target, size_t size, const char *tag) {
GLPushBuffer *push = new GLPushBuffer(this, target, size, tag);
RegisterPushBuffer(frame, push);
return push;
}
@ -565,7 +409,8 @@ public:
void BufferSubdata(GLRBuffer *buffer, size_t offset, size_t size, uint8_t *data, bool deleteData = true) {
// TODO: Maybe should be a render command instead of an init command? When possible it's better as
// an init command, that's for sure.
GLRInitStep step{ GLRInitStepType::BUFFER_SUBDATA };
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::BUFFER_SUBDATA;
_dbg_assert_(offset >= 0);
_dbg_assert_(offset <= buffer->size_ - size);
step.buffer_subdata.buffer = buffer;
@ -573,12 +418,12 @@ public:
step.buffer_subdata.size = (int)size;
step.buffer_subdata.data = data;
step.buffer_subdata.deleteData = deleteData;
initSteps_.push_back(step);
}
// Takes ownership over the data pointer and delete[]-s it.
void TextureImage(GLRTexture *texture, int level, int width, int height, int depth, Draw::DataFormat format, uint8_t *data, GLRAllocType allocType = GLRAllocType::NEW, bool linearFilter = false) {
GLRInitStep step{ GLRInitStepType::TEXTURE_IMAGE };
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::TEXTURE_IMAGE;
step.texture_image.texture = texture;
step.texture_image.data = data;
step.texture_image.format = format;
@ -588,12 +433,11 @@ public:
step.texture_image.depth = depth;
step.texture_image.allocType = allocType;
step.texture_image.linearFilter = linearFilter;
initSteps_.push_back(step);
}
void TextureSubImage(int slot, GLRTexture *texture, int level, int x, int y, int width, int height, Draw::DataFormat format, uint8_t *data, GLRAllocType allocType = GLRAllocType::NEW) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData _data{ GLRRenderCommand::TEXTURE_SUBIMAGE };
GLRRenderData _data(GLRRenderCommand::TEXTURE_SUBIMAGE);
_data.texture_subimage.texture = texture;
_data.texture_subimage.data = data;
_data.texture_subimage.format = format;
@ -608,11 +452,11 @@ public:
}
void FinalizeTexture(GLRTexture *texture, int loadedLevels, bool genMips) {
GLRInitStep step{ GLRInitStepType::TEXTURE_FINALIZE };
GLRInitStep &step = initSteps_.push_uninitialized();
step.stepType = GLRInitStepType::TEXTURE_FINALIZE;
step.texture_finalize.texture = texture;
step.texture_finalize.loadedLevels = loadedLevels;
step.texture_finalize.genMips = genMips;
initSteps_.push_back(step);
}
void BindTexture(int slot, GLRTexture *tex) {
@ -623,62 +467,44 @@ public:
}
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::BINDTEXTURE };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::BINDTEXTURE;
data.texture.slot = slot;
data.texture.texture = tex;
curRenderStep_->commands.push_back(data);
}
void BindProgram(GLRProgram *program) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::BINDPROGRAM };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::BINDPROGRAM;
_dbg_assert_(program != nullptr);
data.program.program = program;
curRenderStep_->commands.push_back(data);
#ifdef _DEBUG
curProgram_ = program;
#endif
}
void BindIndexBuffer(GLRBuffer *buffer) { // Want to support an offset but can't in ES 2.0. We supply an offset when binding the buffers instead.
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::BIND_BUFFER};
data.bind_buffer.buffer = buffer;
data.bind_buffer.target = GL_ELEMENT_ARRAY_BUFFER;
curRenderStep_->commands.push_back(data);
}
void BindVertexBuffer(GLRInputLayout *inputLayout, GLRBuffer *buffer, size_t offset) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(inputLayout);
GLRRenderData data{ GLRRenderCommand::BIND_VERTEX_BUFFER };
data.bindVertexBuffer.inputLayout = inputLayout;
data.bindVertexBuffer.offset = offset;
data.bindVertexBuffer.buffer = buffer;
curRenderStep_->commands.push_back(data);
}
void SetDepth(bool enabled, bool write, GLenum func) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::DEPTH };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::DEPTH;
data.depth.enabled = enabled;
data.depth.write = write;
data.depth.func = func;
curRenderStep_->commands.push_back(data);
}
void SetViewport(const GLRViewport &vp) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::VIEWPORT };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::VIEWPORT;
data.viewport.vp = vp;
curRenderStep_->commands.push_back(data);
}
void SetScissor(const GLRect2D &rc) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::SCISSOR };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::SCISSOR;
data.scissor.rc = rc;
curRenderStep_->commands.push_back(data);
}
void SetUniformI(const GLint *loc, int count, const int *udata) {
@ -686,11 +512,12 @@ public:
#ifdef _DEBUG
_dbg_assert_(curProgram_);
#endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4I };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4I;
data.uniform4.name = nullptr;
data.uniform4.loc = loc;
data.uniform4.count = count;
memcpy(data.uniform4.v, udata, sizeof(int) * count);
curRenderStep_->commands.push_back(data);
}
void SetUniformI1(const GLint *loc, int udata) {
@ -698,11 +525,12 @@ public:
#ifdef _DEBUG
_dbg_assert_(curProgram_);
#endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4I };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4I;
data.uniform4.name = nullptr;
data.uniform4.loc = loc;
data.uniform4.count = 1;
memcpy(data.uniform4.v, &udata, sizeof(udata));
curRenderStep_->commands.push_back(data);
}
void SetUniformUI(const GLint *loc, int count, const uint32_t *udata) {
@ -710,11 +538,12 @@ public:
#ifdef _DEBUG
_dbg_assert_(curProgram_);
#endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4UI };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4UI;
data.uniform4.name = nullptr;
data.uniform4.loc = loc;
data.uniform4.count = count;
memcpy(data.uniform4.v, udata, sizeof(uint32_t) * count);
curRenderStep_->commands.push_back(data);
}
void SetUniformUI1(const GLint *loc, uint32_t udata) {
@ -722,11 +551,12 @@ public:
#ifdef _DEBUG
_dbg_assert_(curProgram_);
#endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4UI };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4UI;
data.uniform4.name = nullptr;
data.uniform4.loc = loc;
data.uniform4.count = 1;
memcpy(data.uniform4.v, &udata, sizeof(udata));
curRenderStep_->commands.push_back(data);
}
void SetUniformF(const GLint *loc, int count, const float *udata) {
@ -734,11 +564,12 @@ public:
#ifdef _DEBUG
_dbg_assert_(curProgram_);
#endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4F };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4F;
data.uniform4.name = nullptr;
data.uniform4.loc = loc;
data.uniform4.count = count;
memcpy(data.uniform4.v, udata, sizeof(float) * count);
curRenderStep_->commands.push_back(data);
}
void SetUniformF1(const GLint *loc, const float udata) {
@ -746,11 +577,12 @@ public:
#ifdef _DEBUG
_dbg_assert_(curProgram_);
#endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4F };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4F;
data.uniform4.name = nullptr;
data.uniform4.loc = loc;
data.uniform4.count = 1;
memcpy(data.uniform4.v, &udata, sizeof(float));
curRenderStep_->commands.push_back(data);
}
void SetUniformF(const char *name, int count, const float *udata) {
@ -758,11 +590,12 @@ public:
#ifdef _DEBUG
_dbg_assert_(curProgram_);
#endif
GLRRenderData data{ GLRRenderCommand::UNIFORM4F };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORM4F;
data.uniform4.name = name;
data.uniform4.loc = nullptr;
data.uniform4.count = count;
memcpy(data.uniform4.v, udata, sizeof(float) * count);
curRenderStep_->commands.push_back(data);
}
void SetUniformM4x4(const GLint *loc, const float *udata) {
@ -770,10 +603,11 @@ public:
#ifdef _DEBUG
_dbg_assert_(curProgram_);
#endif
GLRRenderData data{ GLRRenderCommand::UNIFORMMATRIX };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORMMATRIX;
data.uniformMatrix4.name = nullptr;
data.uniformMatrix4.loc = loc;
memcpy(data.uniformMatrix4.m, udata, sizeof(float) * 16);
curRenderStep_->commands.push_back(data);
}
void SetUniformM4x4Stereo(const char *name, const GLint *loc, const float *left, const float *right) {
@ -781,12 +615,13 @@ public:
#ifdef _DEBUG
_dbg_assert_(curProgram_);
#endif
GLRRenderData data{ GLRRenderCommand::UNIFORMSTEREOMATRIX };
data.uniformMatrix4.name = name;
data.uniformMatrix4.loc = loc;
memcpy(&data.uniformMatrix4.m[0], left, sizeof(float) * 16);
memcpy(&data.uniformMatrix4.m[16], right, sizeof(float) * 16);
curRenderStep_->commands.push_back(data);
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORMSTEREOMATRIX;
data.uniformStereoMatrix4.name = name;
data.uniformStereoMatrix4.loc = loc;
data.uniformStereoMatrix4.mData = new float[32];
memcpy(&data.uniformStereoMatrix4.mData[0], left, sizeof(float) * 16);
memcpy(&data.uniformStereoMatrix4.mData[16], right, sizeof(float) * 16);
}
void SetUniformM4x4(const char *name, const float *udata) {
@ -794,17 +629,19 @@ public:
#ifdef _DEBUG
_dbg_assert_(curProgram_);
#endif
GLRRenderData data{ GLRRenderCommand::UNIFORMMATRIX };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::UNIFORMMATRIX;
data.uniformMatrix4.name = name;
data.uniformMatrix4.loc = nullptr;
memcpy(data.uniformMatrix4.m, udata, sizeof(float) * 16);
curRenderStep_->commands.push_back(data);
}
void SetBlendAndMask(int colorMask, bool blendEnabled, GLenum srcColor, GLenum dstColor, GLenum srcAlpha, GLenum dstAlpha, GLenum funcColor, GLenum funcAlpha) {
// Make this one only a non-debug _assert_, since it often comes first.
// Lets us collect info about this potential crash through assert extra data.
_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::BLEND };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::BLEND;
data.blend.mask = colorMask;
data.blend.enabled = blendEnabled;
data.blend.srcColor = srcColor;
@ -813,96 +650,88 @@ public:
data.blend.dstAlpha = dstAlpha;
data.blend.funcColor = funcColor;
data.blend.funcAlpha = funcAlpha;
curRenderStep_->commands.push_back(data);
}
void SetNoBlendAndMask(int colorMask) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::BLEND };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::BLEND;
data.blend.mask = colorMask;
data.blend.enabled = false;
curRenderStep_->commands.push_back(data);
}
#ifndef USING_GLES2
void SetLogicOp(bool enabled, GLenum logicOp) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::LOGICOP };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::LOGICOP;
data.logic.enabled = enabled;
data.logic.logicOp = logicOp;
curRenderStep_->commands.push_back(data);
}
#endif
void SetStencilFunc(bool enabled, GLenum func, uint8_t refValue, uint8_t compareMask) {
void SetStencil(bool enabled, GLenum func, uint8_t refValue, uint8_t compareMask, uint8_t writeMask, GLenum sFail, GLenum zFail, GLenum pass) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::STENCILFUNC };
data.stencilFunc.enabled = enabled;
data.stencilFunc.func = func;
data.stencilFunc.ref = refValue;
data.stencilFunc.compareMask = compareMask;
curRenderStep_->commands.push_back(data);
}
void SetStencilOp(uint8_t writeMask, GLenum sFail, GLenum zFail, GLenum pass) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::STENCILOP };
data.stencilOp.writeMask = writeMask;
data.stencilOp.sFail = sFail;
data.stencilOp.zFail = zFail;
data.stencilOp.pass = pass;
curRenderStep_->commands.push_back(data);
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::STENCIL;
data.stencil.enabled = enabled;
data.stencil.func = func;
data.stencil.ref = refValue;
data.stencil.compareMask = compareMask;
data.stencil.writeMask = writeMask;
data.stencil.sFail = sFail;
data.stencil.zFail = zFail;
data.stencil.pass = pass;
}
void SetStencilDisabled() {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data;
data.cmd = GLRRenderCommand::STENCILFUNC;
data.stencilFunc.enabled = false;
curRenderStep_->commands.push_back(data);
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::STENCIL;
data.stencil.enabled = false;
}
void SetBlendFactor(const float color[4]) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::BLENDCOLOR };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::BLENDCOLOR;
CopyFloat4(data.blendColor.color, color);
curRenderStep_->commands.push_back(data);
}
void SetRaster(GLboolean cullEnable, GLenum frontFace, GLenum cullFace, GLboolean ditherEnable, GLboolean depthClamp) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::RASTER };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::RASTER;
data.raster.cullEnable = cullEnable;
data.raster.frontFace = frontFace;
data.raster.cullFace = cullFace;
data.raster.ditherEnable = ditherEnable;
data.raster.depthClampEnable = depthClamp;
curRenderStep_->commands.push_back(data);
}
// Modifies the current texture as per GL specs, not global state.
void SetTextureSampler(int slot, GLenum wrapS, GLenum wrapT, GLenum magFilter, GLenum minFilter, float anisotropy) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::TEXTURESAMPLER };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::TEXTURESAMPLER;
data.textureSampler.slot = slot;
data.textureSampler.wrapS = wrapS;
data.textureSampler.wrapT = wrapT;
data.textureSampler.magFilter = magFilter;
data.textureSampler.minFilter = minFilter;
data.textureSampler.anisotropy = anisotropy;
curRenderStep_->commands.push_back(data);
}
void SetTextureLod(int slot, float minLod, float maxLod, float lodBias) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
_dbg_assert_(slot < MAX_GL_TEXTURE_SLOTS);
GLRRenderData data{ GLRRenderCommand::TEXTURELOD};
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::TEXTURELOD;
data.textureLod.slot = slot;
data.textureLod.minLod = minLod;
data.textureLod.maxLod = maxLod;
data.textureLod.lodBias = lodBias;
curRenderStep_->commands.push_back(data);
}
// If scissorW == 0, no scissor is applied (the whole render target is cleared).
@ -910,7 +739,8 @@ public:
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
if (!clearMask)
return;
GLRRenderData data{ GLRRenderCommand::CLEAR };
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::CLEAR;
data.clear.clearMask = clearMask;
data.clear.clearColor = clearColor;
data.clear.clearZ = clearZ;
@ -920,30 +750,36 @@ public:
data.clear.scissorY = scissorY;
data.clear.scissorW = scissorW;
data.clear.scissorH = scissorH;
curRenderStep_->commands.push_back(data);
}
void Draw(GLenum mode, int first, int count) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::DRAW };
void Draw(GLRInputLayout *inputLayout, GLRBuffer *vertexBuffer, uint32_t vertexOffset, GLenum mode, int first, int count) {
_dbg_assert_(vertexBuffer && curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::DRAW;
data.draw.inputLayout = inputLayout;
data.draw.vertexOffset = vertexOffset;
data.draw.vertexBuffer = vertexBuffer;
data.draw.indexBuffer = nullptr;
data.draw.mode = mode;
data.draw.first = first;
data.draw.count = count;
data.draw.buffer = 0;
curRenderStep_->commands.push_back(data);
curRenderStep_->render.numDraws++;
data.draw.indexType = 0;
}
void DrawIndexed(GLenum mode, int count, GLenum indexType, void *indices, int instances = 1) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData data{ GLRRenderCommand::DRAW_INDEXED };
data.drawIndexed.mode = mode;
data.drawIndexed.count = count;
data.drawIndexed.indexType = indexType;
data.drawIndexed.instances = instances;
data.drawIndexed.indices = indices;
curRenderStep_->commands.push_back(data);
curRenderStep_->render.numDraws++;
// Would really love to have a basevertex parameter, but impossible in unextended GLES, without glDrawElementsBaseVertex, unfortunately.
void DrawIndexed(GLRInputLayout *inputLayout, GLRBuffer *vertexBuffer, uint32_t vertexOffset, GLRBuffer *indexBuffer, uint32_t indexOffset, GLenum mode, int count, GLenum indexType, int instances = 1) {
_dbg_assert_(vertexBuffer && indexBuffer && curRenderStep_ && curRenderStep_->stepType == GLRStepType::RENDER);
GLRRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = GLRRenderCommand::DRAW;
data.draw.inputLayout = inputLayout;
data.draw.vertexOffset = vertexOffset;
data.draw.vertexBuffer = vertexBuffer;
data.draw.indexBuffer = indexBuffer;
data.draw.indexOffset = indexOffset;
data.draw.mode = mode;
data.draw.count = count;
data.draw.indexType = indexType;
data.draw.instances = instances;
}
enum { MAX_INFLIGHT_FRAMES = 3 };
@ -1026,7 +862,7 @@ private:
GLRStep *curRenderStep_ = nullptr;
std::vector<GLRStep *> steps_;
std::vector<GLRInitStep> initSteps_;
FastVec<GLRInitStep> initSteps_;
// Execution time state
bool run_ = true;
@ -1038,7 +874,7 @@ private:
std::mutex pushMutex_;
std::condition_variable pushCondVar_;
std::queue<GLRRenderThreadTask> renderThreadQueue_;
std::queue<GLRRenderThreadTask *> renderThreadQueue_;
// For readbacks and other reasons we need to sync with the render thread.
std::mutex syncMutex_;
@ -1072,5 +908,6 @@ private:
#endif
Draw::DeviceCaps caps_{};
std::string profilePassesString_;
InvalidationCallback invalidationCallback_;
};

View file

@ -180,8 +180,9 @@ public:
void Apply(GLRenderManager *render, uint8_t stencilRef, uint8_t stencilWriteMask, uint8_t stencilCompareMask) {
render->SetDepth(depthTestEnabled, depthWriteEnabled, depthComp);
render->SetStencilFunc(stencilEnabled, stencilCompareOp, stencilRef, stencilCompareMask);
render->SetStencilOp(stencilWriteMask, stencilFail, stencilZFail, stencilPass);
render->SetStencil(
stencilEnabled, stencilCompareOp, stencilRef, stencilCompareMask,
stencilWriteMask, stencilFail, stencilZFail, stencilPass);
}
};
@ -328,6 +329,9 @@ public:
DrawContext::SetTargetSize(w, h);
renderManager_.Resize(w, h);
}
void SetDebugFlags(DebugFlags flags) override {
debugFlags_ = flags;
}
const DeviceCaps &GetDeviceCaps() const override {
return caps_;
@ -366,7 +370,12 @@ public:
void BeginFrame() override;
void EndFrame() override;
int GetFrameCount() override {
return frameCount_;
}
void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override;
void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override;
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override;
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) override;
@ -404,12 +413,11 @@ public:
stencilWriteMask_ = writeMask;
stencilCompareMask_ = compareMask;
// Do we need to update on the fly here?
renderManager_.SetStencilFunc(
renderManager_.SetStencil(
curPipeline_->depthStencil->stencilEnabled,
curPipeline_->depthStencil->stencilCompareOp,
refValue,
compareMask);
renderManager_.SetStencilOp(
compareMask,
writeMask,
curPipeline_->depthStencil->stencilFail,
curPipeline_->depthStencil->stencilZFail,
@ -488,6 +496,7 @@ private:
void ApplySamplers();
GLRenderManager renderManager_;
int frameCount_ = 0;
DeviceCaps caps_{};
@ -514,6 +523,8 @@ private:
GLPushBuffer *push;
};
FrameData frameData_[GLRenderManager::MAX_INFLIGHT_FRAMES]{};
DebugFlags debugFlags_ = DebugFlags::NONE;
};
static constexpr int MakeIntelSimpleVer(int v1, int v2, int v3) {
@ -626,7 +637,7 @@ OpenGLContext::OpenGLContext() {
caps_.isTilingGPU = gl_extensions.IsGLES && caps_.vendor != GPUVendor::VENDOR_NVIDIA && caps_.vendor != GPUVendor::VENDOR_INTEL;
for (int i = 0; i < GLRenderManager::MAX_INFLIGHT_FRAMES; i++) {
frameData_[i].push = renderManager_.CreatePushBuffer(i, GL_ARRAY_BUFFER, 64 * 1024);
frameData_[i].push = renderManager_.CreatePushBuffer(i, GL_ARRAY_BUFFER, 64 * 1024, "thin3d_vbuf");
}
if (!gl_extensions.VersionGEThan(3, 0, 0)) {
@ -778,7 +789,7 @@ OpenGLContext::~OpenGLContext() {
}
void OpenGLContext::BeginFrame() {
renderManager_.BeginFrame();
renderManager_.BeginFrame(debugFlags_ & DebugFlags::PROFILE_TIMESTAMPS);
FrameData &frameData = frameData_[renderManager_.GetCurFrame()];
renderManager_.BeginPushBuffer(frameData.push);
}
@ -789,6 +800,7 @@ void OpenGLContext::EndFrame() {
renderManager_.Finish();
Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
frameCount_++;
}
void OpenGLContext::Invalidate(InvalidationFlags flags) {
@ -810,7 +822,7 @@ InputLayout *OpenGLContext::CreateInputLayout(const InputLayoutDesc &desc) {
return fmt;
}
GLuint TypeToTarget(TextureType type) {
static GLuint TypeToTarget(TextureType type) {
switch (type) {
#ifndef USING_GLES2
case TextureType::LINEAR1D: return GL_TEXTURE_1D;
@ -848,25 +860,33 @@ public:
return tex_;
}
void UpdateTextureLevels(GLRenderManager *render, const uint8_t *const *data, int numLevels, TextureCallback initDataCallback);
private:
void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback callback);
void SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback initDataCallback);
GLRenderManager *render_;
GLRTexture *tex_;
DataFormat format_;
TextureType type_;
int mipLevels_;
bool generatedMips_;
bool generateMips_; // Generate mips requested
bool generatedMips_; // Has generated mips
};
OpenGLTexture::OpenGLTexture(GLRenderManager *render, const TextureDesc &desc) : render_(render) {
_dbg_assert_(desc.format != Draw::DataFormat::UNDEFINED);
_dbg_assert_(desc.width > 0 && desc.height > 0 && desc.depth > 0);
_dbg_assert_(desc.type != Draw::TextureType::UNKNOWN);
generatedMips_ = false;
generateMips_ = desc.generateMips;
width_ = desc.width;
height_ = desc.height;
depth_ = desc.depth;
format_ = desc.format;
type_ = desc.type;
GLenum target = TypeToTarget(desc.type);
tex_ = render->CreateTexture(target, desc.width, desc.height, 1, desc.mipLevels);
@ -874,21 +894,25 @@ OpenGLTexture::OpenGLTexture(GLRenderManager *render, const TextureDesc &desc) :
if (desc.initData.empty())
return;
UpdateTextureLevels(render, desc.initData.data(), (int)desc.initData.size(), desc.initDataCallback);
}
void OpenGLTexture::UpdateTextureLevels(GLRenderManager *render, const uint8_t * const *data, int numLevels, TextureCallback initDataCallback) {
int level = 0;
int width = width_;
int height = height_;
int depth = depth_;
for (auto data : desc.initData) {
SetImageData(0, 0, 0, width, height, depth, level, 0, data, desc.initDataCallback);
for (int i = 0; i < numLevels; i++) {
SetImageData(0, 0, 0, width, height, depth, level, 0, data[i], initDataCallback);
width = (width + 1) / 2;
height = (height + 1) / 2;
depth = (depth + 1) / 2;
level++;
}
mipLevels_ = desc.generateMips ? desc.mipLevels : level;
mipLevels_ = generateMips_ ? mipLevels_ : level;
bool genMips = false;
if ((int)desc.initData.size() < desc.mipLevels && desc.generateMips) {
if (numLevels < mipLevels_ && generateMips_) {
// Assumes the texture is bound for editing
genMips = true;
generatedMips_ = true;
@ -918,7 +942,7 @@ public:
GLRFramebuffer *framebuffer_ = nullptr;
};
void OpenGLTexture::SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback callback) {
void OpenGLTexture::SetImageData(int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data, TextureCallback initDataCallback) {
if ((width != width_ || height != height_ || depth != depth_) && level == 0) {
// When switching to texStorage we need to handle this correctly.
width_ = width;
@ -934,8 +958,8 @@ void OpenGLTexture::SetImageData(int x, int y, int z, int width, int height, int
uint8_t *texData = new uint8_t[(size_t)(width * height * depth * alignment)];
bool texDataPopulated = false;
if (callback) {
texDataPopulated = callback(texData, data, width, height, depth, width * (int)alignment, height * width * (int)alignment);
if (initDataCallback) {
texDataPopulated = initDataCallback(texData, data, width, height, depth, width * (int)alignment, height * width * (int)alignment);
}
if (texDataPopulated) {
if (format_ == DataFormat::A1R5G5B5_UNORM_PACK16) {
@ -1016,6 +1040,11 @@ Texture *OpenGLContext::CreateTexture(const TextureDesc &desc) {
return new OpenGLTexture(&renderManager_, desc);
}
void OpenGLContext::UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) {
OpenGLTexture *tex = (OpenGLTexture *)texture;
tex->UpdateTextureLevels(&renderManager_, data, numLevels, initDataCallback);
}
DepthStencilState *OpenGLContext::CreateDepthStencilState(const DepthStencilStateDesc &desc) {
OpenGLDepthStencilState *ds = new OpenGLDepthStencilState();
ds->depthTestEnabled = desc.depthTestEnabled;
@ -1332,38 +1361,37 @@ void OpenGLContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) {
void OpenGLContext::Draw(int vertexCount, int offset) {
_dbg_assert_msg_(curVBuffers_[0] != nullptr, "Can't call Draw without a vertex buffer");
ApplySamplers();
if (curPipeline_->inputLayout) {
renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]);
}
renderManager_.Draw(curPipeline_->prim, offset, vertexCount);
_assert_(curPipeline_->inputLayout);
renderManager_.Draw(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0], curPipeline_->prim, offset, vertexCount);
}
void OpenGLContext::DrawIndexed(int vertexCount, int offset) {
_dbg_assert_msg_(curVBuffers_[0] != nullptr, "Can't call DrawIndexed without a vertex buffer");
_dbg_assert_msg_(curIBuffer_ != nullptr, "Can't call DrawIndexed without an index buffer");
ApplySamplers();
if (curPipeline_->inputLayout) {
renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, curVBuffers_[0]->buffer_, curVBufferOffsets_[0]);
}
renderManager_.BindIndexBuffer(curIBuffer_->buffer_);
renderManager_.DrawIndexed(curPipeline_->prim, vertexCount, GL_UNSIGNED_SHORT, (void *)((intptr_t)curIBufferOffset_ + offset * sizeof(uint32_t)));
_assert_(curPipeline_->inputLayout);
renderManager_.DrawIndexed(
curPipeline_->inputLayout->inputLayout_,
curVBuffers_[0]->buffer_, curVBufferOffsets_[0],
curIBuffer_->buffer_, curIBufferOffset_ + offset * sizeof(uint32_t),
curPipeline_->prim, vertexCount, GL_UNSIGNED_SHORT);
}
void OpenGLContext::DrawUP(const void *vdata, int vertexCount) {
_assert_(curPipeline_->inputLayout != nullptr);
int stride = curPipeline_->inputLayout->stride;
size_t dataSize = stride * vertexCount;
uint32_t dataSize = stride * vertexCount;
FrameData &frameData = frameData_[renderManager_.GetCurFrame()];
GLRBuffer *buf;
size_t offset = frameData.push->Push(vdata, dataSize, &buf);
uint32_t offset;
uint8_t *dest = frameData.push->Allocate(dataSize, 4, &buf, &offset);
memcpy(dest, vdata, dataSize);
ApplySamplers();
if (curPipeline_->inputLayout) {
renderManager_.BindVertexBuffer(curPipeline_->inputLayout->inputLayout_, buf, offset);
}
renderManager_.Draw(curPipeline_->prim, 0, vertexCount);
_assert_(curPipeline_->inputLayout);
renderManager_.Draw(curPipeline_->inputLayout->inputLayout_, buf, offset, curPipeline_->prim, 0, vertexCount);
}
void OpenGLContext::Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) {
@ -1443,7 +1471,7 @@ Framebuffer *OpenGLContext::CreateFramebuffer(const FramebufferDesc &desc) {
// TODO: Support multiview later. (It's our only use case for multi layers).
_dbg_assert_(desc.numLayers == 1);
GLRFramebuffer *framebuffer = renderManager_.CreateFramebuffer(desc.width, desc.height, desc.z_stencil);
GLRFramebuffer *framebuffer = renderManager_.CreateFramebuffer(desc.width, desc.height, desc.z_stencil, desc.tag);
OpenGLFramebuffer *fbo = new OpenGLFramebuffer(&renderManager_, framebuffer);
return fbo;
}

View file

@ -592,10 +592,18 @@ void VulkanContext::ChooseDevice(int physical_device) {
VkPhysicalDeviceFeatures2 features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR};
// Add to chain even if not supported, GetPhysicalDeviceFeatures is supposed to ignore unknown structs.
VkPhysicalDeviceMultiviewFeatures multiViewFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES };
VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR };
VkPhysicalDevicePresentIdFeaturesKHR presentIdFeatures{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR };
features2.pNext = &multiViewFeatures;
multiViewFeatures.pNext = &presentWaitFeatures;
presentWaitFeatures.pNext = &presentIdFeatures;
vkGetPhysicalDeviceFeatures2KHR(physical_devices_[physical_device_], &features2);
deviceFeatures_.available.standard = features2.features;
deviceFeatures_.available.multiview = multiViewFeatures;
deviceFeatures_.available.presentWait = presentWaitFeatures;
deviceFeatures_.available.presentId = presentIdFeatures;
} else {
vkGetPhysicalDeviceFeatures(physical_devices_[physical_device_], &deviceFeatures_.available.standard);
deviceFeatures_.available.multiview = {};
@ -615,6 +623,8 @@ void VulkanContext::ChooseDevice(int physical_device) {
deviceFeatures_.enabled.multiview = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES };
deviceFeatures_.enabled.multiview.multiview = deviceFeatures_.available.multiview.multiview;
deviceFeatures_.enabled.presentId = deviceFeatures_.available.presentId;
deviceFeatures_.enabled.presentWait = deviceFeatures_.available.presentWait;
// deviceFeatures_.enabled.multiview.multiviewGeometryShader = deviceFeatures_.available.multiview.multiviewGeometryShader;
GetDeviceLayerExtensionList(nullptr, device_extension_properties_);
@ -681,6 +691,10 @@ VkResult VulkanContext::CreateDevice() {
extensionsLookup_.EXT_fragment_shader_interlock = EnableDeviceExtension(VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME);
extensionsLookup_.ARM_rasterization_order_attachment_access = EnableDeviceExtension(VK_ARM_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_EXTENSION_NAME);
extensionsLookup_.KHR_present_id = EnableDeviceExtension(VK_KHR_PRESENT_ID_EXTENSION_NAME);
extensionsLookup_.KHR_present_wait = EnableDeviceExtension(VK_KHR_PRESENT_WAIT_EXTENSION_NAME);
extensionsLookup_.GOOGLE_display_timing = EnableDeviceExtension(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME);
VkPhysicalDeviceFeatures2 features2{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 };
VkDeviceCreateInfo device_info{ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
@ -1199,6 +1213,12 @@ static std::string surface_transforms_to_string(VkSurfaceTransformFlagsKHR trans
}
bool VulkanContext::InitSwapchain() {
_assert_(physical_device_ >= 0 && physical_device_ < physical_devices_.size());
if (!surface_) {
ERROR_LOG(G3D, "VK: No surface, can't create swapchain");
return false;
}
VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_devices_[physical_device_], surface_, &surfCapabilities_);
if (res == VK_ERROR_SURFACE_LOST_KHR) {
// Not much to do.

View file

@ -264,6 +264,8 @@ public:
struct AllPhysicalDeviceFeatures {
VkPhysicalDeviceFeatures standard;
VkPhysicalDeviceMultiviewFeatures multiview;
VkPhysicalDevicePresentWaitFeaturesKHR presentWait;
VkPhysicalDevicePresentIdFeaturesKHR presentId;
};
const PhysicalDeviceProps &GetPhysicalDeviceProperties(int i = -1) const {

View file

@ -53,8 +53,14 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback(
return false;
case 1303270965:
// Benign perf warning, image blit using GENERAL layout.
// UNASSIGNED
// TODO: Oops, turns out we filtered out a bit too much here!
// We really need that performance flag check to sort out the stuff that matters.
// Will enable it soon, but it'll take some fixing.
//
if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)
return false;
break;
case 606910136:
case -392708513:
case -384083808:

View file

@ -121,7 +121,7 @@ VkCommandBuffer FrameData::GetInitCmd(VulkanContext *vulkan) {
}
// Good spot to reset the query pool.
if (profilingEnabled_) {
if (profile.enabled) {
vkCmdResetQueryPool(initCmd, profile.queryPool, 0, MAX_TIMESTAMP_QUERIES);
vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, profile.queryPool, 0);
}
@ -138,7 +138,7 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame
VkFence fenceToTrigger = VK_NULL_HANDLE;
if (hasInitCommands) {
if (profilingEnabled_) {
if (profile.enabled) {
// Pre-allocated query ID 1 - end of init cmdbuf.
vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile.queryPool, 1);
}

View file

@ -19,11 +19,14 @@ enum class VKRRunType {
};
struct QueueProfileContext {
bool enabled = false;
bool timestampsEnabled = false;
VkQueryPool queryPool;
std::vector<std::string> timestampDescriptions;
std::string profileSummary;
double cpuStartTime;
double cpuEndTime;
double descWriteTime;
};
class VKRFramebuffer;
@ -92,8 +95,7 @@ struct FrameData {
uint32_t curSwapchainImage = -1;
// Profiling.
QueueProfileContext profile;
bool profilingEnabled_ = false;
QueueProfileContext profile{};
// Async readback cache.
DenseHashMap<ReadbackKey, CachedReadback*, nullptr> readbacks_;

View file

@ -265,7 +265,6 @@ static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) {
// Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType, VkSampleCountFlagBits sampleCount) {
bool selfDependency = RenderPassTypeHasInput(rpType);
bool isBackbuffer = rpType == RenderPassType::BACKBUFFER;
bool hasDepth = RenderPassTypeHasDepth(rpType);
bool multiview = RenderPassTypeHasMultiView(rpType);
@ -330,7 +329,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
VkAttachmentReference colorReference{};
colorReference.attachment = colorAttachmentIndex;
colorReference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depthReference{};
depthReference.attachment = depthAttachmentIndex;
@ -339,20 +338,15 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.flags = 0;
if (selfDependency) {
subpass.inputAttachmentCount = 1;
subpass.pInputAttachments = &colorReference;
} else {
subpass.inputAttachmentCount = 0;
subpass.pInputAttachments = nullptr;
}
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorReference;
VkAttachmentReference colorResolveReference;
if (multisample) {
colorResolveReference.attachment = 0; // the non-msaa color buffer.
colorResolveReference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorResolveReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
subpass.pResolveAttachments = &colorResolveReference;
} else {
subpass.pResolveAttachments = nullptr;
@ -396,17 +390,6 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
numDeps++;
}
if (selfDependency) {
deps[numDeps].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
deps[numDeps].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
deps[numDeps].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
deps[numDeps].srcSubpass = 0;
deps[numDeps].dstSubpass = 0;
numDeps++;
}
if (numDeps > 0) {
rp.dependencyCount = (u32)numDeps;
rp.pDependencies = deps;
@ -463,10 +446,6 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
VkSubpassDescription2KHR subpass2{ VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR };
subpass2.colorAttachmentCount = subpass.colorAttachmentCount;
subpass2.flags = subpass.flags;
if (selfDependency) {
subpass2.inputAttachmentCount = subpass.inputAttachmentCount;
subpass2.pInputAttachments = &colorReference2;
}
subpass2.pColorAttachments = &colorReference2;
if (hasDepth) {
subpass2.pDepthStencilAttachment = &depthReference2;
@ -476,7 +455,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
if (multisample) {
colorResolveReference2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
colorResolveReference2.attachment = colorResolveReference.attachment; // the non-msaa color buffer.
colorResolveReference2.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorResolveReference2.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
subpass2.pResolveAttachments = &colorResolveReference2;
} else {
subpass2.pResolveAttachments = nullptr;

View file

@ -13,15 +13,14 @@ enum class RenderPassType {
// These eight are organized so that bit 0 is DEPTH and bit 1 is INPUT and bit 2 is MULTIVIEW, so
// they can be OR-ed together in MergeRPTypes.
HAS_DEPTH = 1,
COLOR_INPUT = 2, // input attachment
MULTIVIEW = 4,
MULTISAMPLE = 8,
MULTIVIEW = 2,
MULTISAMPLE = 4,
// This is the odd one out, and gets special handling in MergeRPTypes.
// If this flag is set, none of the other flags can be set.
// For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible
// so we don't bother with a non-depth version.
BACKBUFFER = 16,
BACKBUFFER = 8,
TYPE_COUNT = BACKBUFFER + 1,
};
@ -107,10 +106,6 @@ inline bool RenderPassTypeHasDepth(RenderPassType type) {
return (type & RenderPassType::HAS_DEPTH) || type == RenderPassType::BACKBUFFER;
}
inline bool RenderPassTypeHasInput(RenderPassType type) {
return (type & RenderPassType::COLOR_INPUT) != 0;
}
inline bool RenderPassTypeHasMultiView(RenderPassType type) {
return (type & RenderPassType::MULTIVIEW) != 0;
}

View file

@ -258,6 +258,22 @@ void VulkanTexture::EndCreate(VkCommandBuffer cmd, bool vertexTexture, VkPipelin
prevStage == VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT ? VK_ACCESS_SHADER_WRITE_BIT : VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
}
void VulkanTexture::PrepareForTransferDst(VkCommandBuffer cmd, int levels) {
TransitionImageLayout2(cmd, image_, 0, levels, 1,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
}
void VulkanTexture::RestoreAfterTransferDst(VkCommandBuffer cmd, int levels) {
TransitionImageLayout2(cmd, image_, 0, levels, 1,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT);
}
VkImageView VulkanTexture::CreateViewForMip(int mip) {
VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
view_info.image = image_;

View file

@ -37,6 +37,10 @@ public:
void GenerateMips(VkCommandBuffer cmd, int firstMipToGenerate, bool fromCompute);
void EndCreate(VkCommandBuffer cmd, bool vertexTexture, VkPipelineStageFlags prevStage, VkImageLayout layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
// For updating levels after creation. Careful with the timelines!
void PrepareForTransferDst(VkCommandBuffer cmd, int levels);
void RestoreAfterTransferDst(VkCommandBuffer cmd, int levels);
// When loading mips from compute shaders, you need to pass VK_IMAGE_LAYOUT_GENERAL to the above function.
// In addition, ignore UploadMip and GenerateMip, and instead use GetViewForMip. Make sure to delete the returned views when used.
VkImageView CreateViewForMip(int mip);

View file

@ -253,6 +253,9 @@ struct VulkanExtensions {
bool EXT_swapchain_colorspace;
bool ARM_rasterization_order_attachment_access;
bool EXT_fragment_shader_interlock;
bool KHR_present_id; // Should probably check the feature flags instead.
bool KHR_present_wait; // Same
bool GOOGLE_display_timing;
// bool EXT_depth_range_unrestricted; // Allows depth outside [0.0, 1.0] in 32-bit float depth buffers.
};

View file

@ -35,37 +35,15 @@ using namespace PPSSPP_VK;
// Always keep around push buffers at least this long (seconds).
static const double PUSH_GARBAGE_COLLECTION_DELAY = 10.0;
// Global push buffer tracker for vulkan memory profiling.
// Don't want to manually dig up all the active push buffers.
static std::mutex g_pushBufferListMutex;
static std::set<VulkanMemoryManager *> g_pushBuffers;
std::vector<VulkanMemoryManager *> GetActiveVulkanMemoryManagers() {
std::vector<VulkanMemoryManager *> buffers;
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
for (auto iter : g_pushBuffers) {
buffers.push_back(iter);
}
return buffers;
}
VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, const char *name, size_t size, VkBufferUsageFlags usage)
: vulkan_(vulkan), name_(name), size_(size), usage_(usage) {
{
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.insert(this);
}
RegisterGPUMemoryManager(this);
bool res = AddBuffer();
_assert_(res);
}
VulkanPushBuffer::~VulkanPushBuffer() {
{
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.erase(this);
}
UnregisterGPUMemoryManager(this);
_dbg_assert_(!writePtr_);
_assert_(buffers_.empty());
}
@ -276,11 +254,7 @@ VkResult VulkanDescSetPool::Recreate(bool grow) {
VulkanPushPool::VulkanPushPool(VulkanContext *vulkan, const char *name, size_t originalBlockSize, VkBufferUsageFlags usage)
: vulkan_(vulkan), name_(name), originalBlockSize_(originalBlockSize), usage_(usage) {
{
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.insert(this);
}
RegisterGPUMemoryManager(this);
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
blocks_.push_back(CreateBlock(originalBlockSize));
blocks_.back().original = true;
@ -289,11 +263,7 @@ VulkanPushPool::VulkanPushPool(VulkanContext *vulkan, const char *name, size_t o
}
VulkanPushPool::~VulkanPushPool() {
{
std::lock_guard<std::mutex> guard(g_pushBufferListMutex);
g_pushBuffers.erase(this);
}
UnregisterGPUMemoryManager(this);
_dbg_assert_(blocks_.empty());
}
@ -323,6 +293,7 @@ VulkanPushPool::Block VulkanPushPool::CreateBlock(size_t size) {
result = vmaMapMemory(vulkan_->Allocator(), block.allocation, (void **)(&block.writePtr));
_assert_(result == VK_SUCCESS);
_assert_msg_(block.writePtr != nullptr, "VulkanPushPool: Failed to map memory on block of size %d", (int)block.size);
return block;
}
@ -384,6 +355,7 @@ void VulkanPushPool::NextBlock(VkDeviceSize allocationSize) {
block.used = allocationSize;
block.lastUsed = time_now_d();
block.frameIndex = curFrameIndex;
_assert_(block.writePtr != nullptr);
return;
}
curBlockIndex_++;
@ -391,6 +363,7 @@ void VulkanPushPool::NextBlock(VkDeviceSize allocationSize) {
double start = time_now_d();
VkDeviceSize newBlockSize = std::max(originalBlockSize_ * 2, (VkDeviceSize)RoundUpToPowerOf2((uint32_t)allocationSize));
// We're still here and ran off the end of blocks. Create a new one.
blocks_.push_back(CreateBlock(newBlockSize));
blocks_.back().frameIndex = curFrameIndex;

View file

@ -5,7 +5,9 @@
#include <functional>
#include <vector>
#include "Common/Data/Collections/FastVec.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/GPUBackendCommon.h"
// Forward declaration
VK_DEFINE_HANDLE(VmaAllocation);
@ -14,22 +16,13 @@ VK_DEFINE_HANDLE(VmaAllocation);
//
// Vulkan memory management utils.
// Just an abstract thing to get debug information.
class VulkanMemoryManager {
public:
virtual ~VulkanMemoryManager() {}
virtual void GetDebugString(char *buffer, size_t bufSize) const = 0;
virtual const char *Name() const = 0; // for sorting
};
// VulkanPushBuffer
// Simple incrementing allocator.
// Use these to push vertex, index and uniform data. Generally you'll have two or three of these
// and alternate on each frame. Make sure not to reset until the fence from the last time you used it
// has completed.
// NOTE: This has now been replaced with VulkanPushPool for all uses except the vertex cache.
class VulkanPushBuffer : public VulkanMemoryManager {
class VulkanPushBuffer : public GPUMemoryManager {
struct BufInfo {
VkBuffer buffer;
VmaAllocation allocation;
@ -107,7 +100,8 @@ private:
// Simple memory pushbuffer pool that can share blocks between the "frames", to reduce the impact of push memory spikes -
// a later frame can gobble up redundant buffers from an earlier frame even if they don't share frame index.
class VulkanPushPool : public VulkanMemoryManager {
// NOT thread safe! Can only be used from one thread (our main thread).
class VulkanPushPool : public GPUMemoryManager {
public:
VulkanPushPool(VulkanContext *vulkan, const char *name, size_t originalBlockSize, VkBufferUsageFlags usage);
~VulkanPushPool();
@ -142,6 +136,8 @@ public:
return blocks_[curBlockIndex_].writePtr;
}
// NOTE: If you can avoid this by writing the data directly into memory returned from Allocate,
// do so. Savings from avoiding memcpy can be significant.
VkDeviceSize Push(const void *data, VkDeviceSize numBytes, int alignment, VkBuffer *vkbuf) {
uint32_t bindOffset;
uint8_t *ptr = Allocate(numBytes, alignment, vkbuf, &bindOffset);
@ -210,6 +206,3 @@ private:
uint32_t usage_ = 0;
bool grow_;
};
std::vector<VulkanMemoryManager *> GetActiveVulkanMemoryManagers();

View file

@ -9,7 +9,7 @@
using namespace PPSSPP_VK;
// Debug help: adb logcat -s DEBUG PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState
// Debug help: adb logcat -s DEBUG AndroidRuntime PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState PpssppActivity CameraHelper
static void MergeRenderAreaRectInto(VkRect2D *dest, const VkRect2D &src) {
if (dest->offset.x > src.offset.x) {
@ -261,31 +261,6 @@ VKRRenderPass *VulkanQueueRunner::GetRenderPass(const RPKey &key) {
return pass;
}
// Must match the subpass self-dependency declared above.
void VulkanQueueRunner::SelfDependencyBarrier(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier) {
if (aspect & VK_IMAGE_ASPECT_COLOR_BIT) {
VkAccessFlags srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkAccessFlags dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
recordBarrier->TransitionImage(
img.image,
0,
1,
img.numLayers,
aspect,
VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_GENERAL,
srcAccessMask,
dstAccessMask,
srcStageMask,
dstStageMask
);
} else {
_assert_msg_(false, "Depth self-dependencies not yet supported");
}
}
void VulkanQueueRunner::PreprocessSteps(std::vector<VKRStep *> &steps) {
// Optimizes renderpasses, then sequences them.
// Planned optimizations:
@ -364,7 +339,7 @@ void VulkanQueueRunner::PreprocessSteps(std::vector<VKRStep *> &steps) {
}
void VulkanQueueRunner::RunSteps(std::vector<VKRStep *> &steps, FrameData &frameData, FrameDataShared &frameDataShared, bool keepSteps) {
QueueProfileContext *profile = frameData.profilingEnabled_ ? &frameData.profile : nullptr;
QueueProfileContext *profile = frameData.profile.enabled ? &frameData.profile : nullptr;
if (profile)
profile->cpuStartTime = time_now_d();
@ -437,7 +412,7 @@ void VulkanQueueRunner::RunSteps(std::vector<VKRStep *> &steps, FrameData &frame
break;
}
if (profile && profile->timestampDescriptions.size() + 1 < MAX_TIMESTAMP_QUERIES) {
if (profile && profile->timestampsEnabled && profile->timestampDescriptions.size() + 1 < MAX_TIMESTAMP_QUERIES) {
vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile->queryPool, (uint32_t)profile->timestampDescriptions.size());
profile->timestampDescriptions.push_back(StepToString(step));
}
@ -481,7 +456,7 @@ void VulkanQueueRunner::ApplyMGSHack(std::vector<VKRStep *> &steps) {
last = j - 1;
// should really also check descriptor sets...
if (steps[j]->commands.size()) {
VkRenderData &cmd = steps[j]->commands.back();
const VkRenderData &cmd = steps[j]->commands.back();
if (cmd.cmd == VKRRenderCommand::DRAW_INDEXED && cmd.draw.count != 6)
last = j - 1;
}
@ -918,9 +893,6 @@ void VulkanQueueRunner::LogRenderPass(const VKRStep &pass, bool verbose) {
case VKRRenderCommand::REMOVED:
INFO_LOG(G3D, " (Removed)");
break;
case VKRRenderCommand::SELF_DEPENDENCY_BARRIER:
INFO_LOG(G3D, " SelfBarrier()");
break;
case VKRRenderCommand::BIND_GRAPHICS_PIPELINE:
INFO_LOG(G3D, " BindGraphicsPipeline(%x)", (int)(intptr_t)cmd.graphics_pipeline.pipeline);
break;
@ -1241,7 +1213,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
VKRGraphicsPipeline *lastGraphicsPipeline = nullptr;
VKRComputePipeline *lastComputePipeline = nullptr;
auto &commands = step.commands;
const auto &commands = step.commands;
// We can do a little bit of state tracking here to eliminate some calls into the driver.
// The stencil ones are very commonly mostly redundant so let's eliminate them where possible.
@ -1361,21 +1333,6 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
break;
}
case VKRRenderCommand::SELF_DEPENDENCY_BARRIER:
{
_assert_(step.render.pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT);
_assert_(fb);
VulkanBarrier barrier;
if (fb->sampleCount != VK_SAMPLE_COUNT_1_BIT) {
// Rendering is happening to the multisample buffer, not the color buffer.
SelfDependencyBarrier(fb->msaaColor, VK_IMAGE_ASPECT_COLOR_BIT, &barrier);
} else {
SelfDependencyBarrier(fb->color, VK_IMAGE_ASPECT_COLOR_BIT, &barrier);
}
barrier.Flush(cmd);
break;
}
case VKRRenderCommand::PUSH_CONSTANTS:
if (pipelineOK) {
vkCmdPushConstants(cmd, pipelineLayout, c.push.stages, c.push.offset, c.push.size, c.push.data);
@ -1400,7 +1357,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
case VKRRenderCommand::DRAW_INDEXED:
if (pipelineOK) {
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &c.drawIndexed.ds, c.drawIndexed.numUboOffsets, c.drawIndexed.uboOffsets);
vkCmdBindIndexBuffer(cmd, c.drawIndexed.ibuffer, c.drawIndexed.ioffset, (VkIndexType)c.drawIndexed.indexType);
vkCmdBindIndexBuffer(cmd, c.drawIndexed.ibuffer, c.drawIndexed.ioffset, VK_INDEX_TYPE_UINT16);
VkDeviceSize voffset = c.drawIndexed.voffset;
vkCmdBindVertexBuffers(cmd, 0, 1, &c.drawIndexed.vbuffer, &voffset);
vkCmdDrawIndexed(cmd, c.drawIndexed.count, c.drawIndexed.instances, 0, 0, 0);

View file

@ -6,6 +6,7 @@
#include "Common/Thread/Promise.h"
#include "Common/Data/Collections/Hashmaps.h"
#include "Common/Data/Collections/FastVec.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanBarrier.h"
#include "Common/GPU/Vulkan/VulkanFrameData.h"
@ -38,7 +39,6 @@ enum class VKRRenderCommand : uint8_t {
DRAW,
DRAW_INDEXED,
PUSH_CONSTANTS,
SELF_DEPENDENCY_BARRIER,
DEBUG_ANNOTATION,
NUM_RENDER_COMMANDS,
};
@ -47,10 +47,9 @@ enum class PipelineFlags : u8 {
NONE = 0,
USES_BLEND_CONSTANT = (1 << 1),
USES_DEPTH_STENCIL = (1 << 2), // Reads or writes the depth or stencil buffers.
USES_INPUT_ATTACHMENT = (1 << 3),
USES_GEOMETRY_SHADER = (1 << 4),
USES_MULTIVIEW = (1 << 5), // Inherited from the render pass it was created with.
USES_DISCARD = (1 << 6),
USES_GEOMETRY_SHADER = (1 << 3),
USES_MULTIVIEW = (1 << 4), // Inherited from the render pass it was created with.
USES_DISCARD = (1 << 5),
};
ENUM_CLASS_BITOPS(PipelineFlags);
@ -80,15 +79,14 @@ struct VkRenderData {
} draw;
struct {
VkDescriptorSet ds;
int numUboOffsets;
uint32_t uboOffsets[3];
uint16_t numUboOffsets;
uint16_t instances;
VkBuffer vbuffer;
VkBuffer ibuffer;
uint32_t voffset;
uint32_t ioffset;
uint32_t count;
int16_t instances;
int16_t indexType;
} drawIndexed;
struct {
uint32_t clearColor;
@ -153,7 +151,7 @@ struct VKRStep {
~VKRStep() {}
VKRStepType stepType;
std::vector<VkRenderData> commands;
FastVec<VkRenderData> commands;
TinySet<TransitionRequest, 4> preTransitions;
TinySet<VKRFramebuffer *, 8> dependencies;
const char *tag;
@ -212,9 +210,14 @@ struct VKRStep {
// These are enqueued from the main thread,
// and the render thread pops them off
struct VKRRenderThreadTask {
VKRRenderThreadTask(VKRRunType _runType) : runType(_runType) {}
std::vector<VKRStep *> steps;
int frame;
int frame = -1;
VKRRunType runType;
// Avoid copying these by accident.
VKRRenderThreadTask(VKRRenderThreadTask &) = delete;
VKRRenderThreadTask &operator =(VKRRenderThreadTask &) = delete;
};
class VulkanQueueRunner {
@ -309,8 +312,6 @@ private:
static void SetupTransitionToTransferDst(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier);
static void SetupTransferDstWriteAfterWrite(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier);
static void SelfDependencyBarrier(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier);
VulkanContext *vulkan_;
VkFramebuffer backbuffer_ = VK_NULL_HANDLE;

View file

@ -308,9 +308,8 @@ bool VulkanRenderManager::CreateBackbuffers() {
void VulkanRenderManager::StopThread() {
{
// Tell the render thread to quit when it's done.
VKRRenderThreadTask task;
task.frame = vulkan_->GetCurFrame();
task.runType = VKRRunType::EXIT;
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::EXIT);
task->frame = vulkan_->GetCurFrame();
std::unique_lock<std::mutex> lock(pushMutex_);
renderThreadQueue_.push(task);
pushCondVar_.notify_one();
@ -494,7 +493,7 @@ void VulkanRenderManager::ThreadFunc() {
SetCurrentThreadName("RenderMan");
while (true) {
// Pop a task of the queue and execute it.
VKRRenderThreadTask task;
VKRRenderThreadTask *task = nullptr;
{
std::unique_lock<std::mutex> lock(pushMutex_);
while (renderThreadQueue_.empty()) {
@ -506,12 +505,15 @@ void VulkanRenderManager::ThreadFunc() {
// Oh, we got a task! We can now have pushMutex_ unlocked, allowing the host to
// push more work when it feels like it, and just start working.
if (task.runType == VKRRunType::EXIT) {
if (task->runType == VKRRunType::EXIT) {
// Oh, host wanted out. Let's leave.
delete task;
// In this case, there should be no more tasks.
break;
}
Run(task);
Run(*task);
delete task;
}
// Wait for the device to be done with everything, before tearing stuff down.
@ -550,13 +552,14 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
int validBits = vulkan_->GetQueueFamilyProperties(vulkan_->GetGraphicsQueueFamilyIndex()).timestampValidBits;
// Can't set this until after the fence.
frameData.profilingEnabled_ = enableProfiling && validBits > 0;
frameData.profile.enabled = enableProfiling;
frameData.profile.timestampsEnabled = enableProfiling && validBits > 0;
uint64_t queryResults[MAX_TIMESTAMP_QUERIES];
if (frameData.profilingEnabled_) {
if (enableProfiling) {
// Pull the profiling results from last time and produce a summary!
if (!frameData.profile.timestampDescriptions.empty()) {
if (!frameData.profile.timestampDescriptions.empty() && frameData.profile.timestampsEnabled) {
int numQueries = (int)frameData.profile.timestampDescriptions.size();
VkResult res = vkGetQueryPoolResults(
vulkan_->GetDevice(),
@ -594,7 +597,12 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
frameData.profile.profileSummary = "(error getting GPU profile - not ready?)";
}
} else {
frameData.profile.profileSummary = "(no GPU profile data collected)";
std::stringstream str;
char line[256];
renderCPUTimeMs_.Update((frameData.profile.cpuEndTime - frameData.profile.cpuStartTime) * 1000.0);
renderCPUTimeMs_.Format(line, sizeof(line));
str << line;
frameData.profile.profileSummary = str.str();
}
}
@ -605,7 +613,7 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
vulkan_->BeginFrame(enableLogProfiler ? GetInitCmd() : VK_NULL_HANDLE);
frameData.profile.timestampDescriptions.clear();
if (frameData.profilingEnabled_) {
if (frameData.profile.timestampsEnabled) {
// For various reasons, we need to always use an init cmd buffer in this case to perform the vkCmdResetQueryPool,
// unless we want to limit ourselves to only measure the main cmd buffer.
// Later versions of Vulkan have support for clearing queries on the CPU timeline, but we don't want to rely on that.
@ -657,10 +665,6 @@ VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipe
WARN_LOG(G3D, "Not compiling pipeline that requires depth, for non depth renderpass type");
continue;
}
if ((pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT) && !RenderPassTypeHasInput(rpType)) {
WARN_LOG(G3D, "Not compiling pipeline that requires input attachment, for non input renderpass type");
continue;
}
// Shouldn't hit this, these should have been filtered elsewhere. However, still a good check to do.
if (sampleCount == VK_SAMPLE_COUNT_1_BIT && RenderPassTypeHasMultisample(rpType)) {
WARN_LOG(G3D, "Not compiling single sample pipeline for a multisampled render pass type");
@ -710,10 +714,6 @@ void VulkanRenderManager::EndCurRenderStep() {
if (!curRenderStep_->render.framebuffer) {
rpType = RenderPassType::BACKBUFFER;
} else {
if (curPipelineFlags_ & PipelineFlags::USES_INPUT_ATTACHMENT) {
// Not allowed on backbuffers.
rpType = depthStencil ? (RenderPassType::HAS_DEPTH | RenderPassType::COLOR_INPUT) : RenderPassType::COLOR_INPUT;
}
// Framebuffers can be stereo, and if so, will control the render pass type to match.
// Pipelines can be mono and render fine to stereo etc, so not checking them here.
// Note that we don't support rendering to just one layer of a multilayer framebuffer!
@ -764,11 +764,6 @@ void VulkanRenderManager::EndCurRenderStep() {
curPipelineFlags_ = (PipelineFlags)0;
}
void VulkanRenderManager::BindCurrentFramebufferAsInputAttachment0(VkImageAspectFlags aspectBits) {
_dbg_assert_(curRenderStep_);
curRenderStep_->commands.push_back(VkRenderData{ VKRRenderCommand::SELF_DEPENDENCY_BARRIER });
}
void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassLoadAction color, VKRRenderPassLoadAction depth, VKRRenderPassLoadAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) {
_dbg_assert_(insideFrame_);
// Eliminate dupes (bind of the framebuffer we already are rendering to), instantly convert to a clear if possible.
@ -999,7 +994,7 @@ void VulkanRenderManager::CopyImageToMemorySync(VkImage image, int mipLevel, int
queueRunner_.CopyReadbackBuffer(frameData_[vulkan_->GetCurFrame()], nullptr, w, h, destFormat, destFormat, pixelStride, pixels);
}
static void RemoveDrawCommands(std::vector<VkRenderData> *cmds) {
static void RemoveDrawCommands(FastVec<VkRenderData> *cmds) {
// Here we remove any DRAW type commands when we hit a CLEAR.
for (auto &c : *cmds) {
if (c.cmd == VKRRenderCommand::DRAW || c.cmd == VKRRenderCommand::DRAW_INDEXED) {
@ -1008,7 +1003,7 @@ static void RemoveDrawCommands(std::vector<VkRenderData> *cmds) {
}
}
static void CleanupRenderCommands(std::vector<VkRenderData> *cmds) {
static void CleanupRenderCommands(FastVec<VkRenderData> *cmds) {
size_t lastCommand[(int)VKRRenderCommand::NUM_RENDER_COMMANDS];
memset(lastCommand, -1, sizeof(lastCommand));
@ -1266,13 +1261,12 @@ void VulkanRenderManager::Finish() {
FrameData &frameData = frameData_[curFrame];
VLOG("PUSH: Frame[%d]", curFrame);
VKRRenderThreadTask task;
task.frame = curFrame;
task.runType = VKRRunType::PRESENT;
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::PRESENT);
task->frame = curFrame;
{
std::unique_lock<std::mutex> lock(pushMutex_);
renderThreadQueue_.push(task);
renderThreadQueue_.back().steps = std::move(steps_);
renderThreadQueue_.back()->steps = std::move(steps_);
pushCondVar_.notify_one();
}
@ -1382,12 +1376,11 @@ void VulkanRenderManager::FlushSync() {
{
VLOG("PUSH: Frame[%d]", curFrame);
VKRRenderThreadTask task;
task.frame = curFrame;
task.runType = VKRRunType::SYNC;
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::SYNC);
task->frame = curFrame;
std::unique_lock<std::mutex> lock(pushMutex_);
renderThreadQueue_.push(task);
renderThreadQueue_.back().steps = std::move(steps_);
renderThreadQueue_.back()->steps = std::move(steps_);
pushCondVar_.notify_one();
}

View file

@ -76,7 +76,10 @@ struct BoundingRect {
// All the data needed to create a graphics pipeline.
// TODO: Compress this down greatly.
struct VKRGraphicsPipelineDesc : Draw::RefCountedObject {
class VKRGraphicsPipelineDesc : public Draw::RefCountedObject {
public:
VKRGraphicsPipelineDesc() : Draw::RefCountedObject("VKRGraphicsPipelineDesc") {}
VkPipelineCache pipelineCache = VK_NULL_HANDLE;
VkPipelineColorBlendStateCreateInfo cbs{ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO };
VkPipelineColorBlendAttachmentState blend0{};
@ -215,8 +218,6 @@ public:
// get an array texture view.
VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBits, int layer);
void BindCurrentFramebufferAsInputAttachment0(VkImageAspectFlags aspectBits);
bool CopyFramebufferToMemory(VKRFramebuffer *src, VkImageAspectFlags aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, Draw::ReadbackMode mode, const char *tag);
void CopyImageToMemorySync(VkImage image, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag);
@ -240,7 +241,8 @@ public:
void BindPipeline(VKRGraphicsPipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(pipeline != nullptr);
VkRenderData data{ VKRRenderCommand::BIND_GRAPHICS_PIPELINE };
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::BIND_GRAPHICS_PIPELINE;
pipelinesToCheck_.push_back(pipeline);
data.graphics_pipeline.pipeline = pipeline;
data.graphics_pipeline.pipelineLayout = pipelineLayout;
@ -249,24 +251,24 @@ public:
// DebugBreak();
// }
curPipelineFlags_ |= flags;
curRenderStep_->commands.push_back(data);
}
void BindPipeline(VKRComputePipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(pipeline != nullptr);
VkRenderData data{ VKRRenderCommand::BIND_COMPUTE_PIPELINE };
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::BIND_COMPUTE_PIPELINE;
data.compute_pipeline.pipeline = pipeline;
data.compute_pipeline.pipelineLayout = pipelineLayout;
curPipelineFlags_ |= flags;
curRenderStep_->commands.push_back(data);
}
void SetViewport(const VkViewport &vp) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_((int)vp.width >= 0);
_dbg_assert_((int)vp.height >= 0);
VkRenderData data{ VKRRenderCommand::VIEWPORT };
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::VIEWPORT;
data.viewport.vp.x = vp.x;
data.viewport.vp.y = vp.y;
data.viewport.vp.width = vp.width;
@ -276,7 +278,6 @@ public:
// TODO: This should be fixed at the source.
data.viewport.vp.minDepth = clamp_value(vp.minDepth, 0.0f, 1.0f);
data.viewport.vp.maxDepth = clamp_value(vp.maxDepth, 0.0f, 1.0f);
curRenderStep_->commands.push_back(data);
curStepHasViewport_ = true;
}
@ -318,37 +319,37 @@ public:
curRenderArea_.Apply(rc);
VkRenderData data{ VKRRenderCommand::SCISSOR };
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::SCISSOR;
data.scissor.scissor = rc;
curRenderStep_->commands.push_back(data);
curStepHasScissor_ = true;
}
void SetStencilParams(uint8_t writeMask, uint8_t compareMask, uint8_t refValue) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
VkRenderData data{ VKRRenderCommand::STENCIL };
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::STENCIL;
data.stencil.stencilWriteMask = writeMask;
data.stencil.stencilCompareMask = compareMask;
data.stencil.stencilRef = refValue;
curRenderStep_->commands.push_back(data);
}
void SetBlendFactor(uint32_t color) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
VkRenderData data{ VKRRenderCommand::BLEND };
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::BLEND;
data.blendColor.color = color;
curRenderStep_->commands.push_back(data);
}
void PushConstants(VkPipelineLayout pipelineLayout, VkShaderStageFlags stages, int offset, int size, void *constants) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(size + offset < 40);
VkRenderData data{ VKRRenderCommand::PUSH_CONSTANTS };
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::PUSH_CONSTANTS;
data.push.stages = stages;
data.push.offset = offset;
data.push.size = size;
memcpy(data.push.data, constants, size);
curRenderStep_->commands.push_back(data);
}
void Clear(uint32_t clearColor, float clearZ, int clearStencil, int clearMask);
@ -380,7 +381,8 @@ public:
void Draw(VkDescriptorSet descSet, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, int count, int offset = 0) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && curStepHasViewport_ && curStepHasScissor_);
VkRenderData data{ VKRRenderCommand::DRAW };
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::DRAW;
data.draw.count = count;
data.draw.offset = offset;
data.draw.ds = descSet;
@ -390,13 +392,13 @@ public:
_dbg_assert_(numUboOffsets <= ARRAY_SIZE(data.draw.uboOffsets));
for (int i = 0; i < numUboOffsets; i++)
data.draw.uboOffsets[i] = uboOffsets[i];
curRenderStep_->commands.push_back(data);
curRenderStep_->render.numDraws++;
}
void DrawIndexed(VkDescriptorSet descSet, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, VkBuffer ibuffer, int ioffset, int count, int numInstances, VkIndexType indexType) {
void DrawIndexed(VkDescriptorSet descSet, int numUboOffsets, const uint32_t *uboOffsets, VkBuffer vbuffer, int voffset, VkBuffer ibuffer, int ioffset, int count, int numInstances) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER && curStepHasViewport_ && curStepHasScissor_);
VkRenderData data{ VKRRenderCommand::DRAW_INDEXED };
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::DRAW_INDEXED;
data.drawIndexed.count = count;
data.drawIndexed.instances = numInstances;
data.drawIndexed.ds = descSet;
@ -408,8 +410,6 @@ public:
_dbg_assert_(numUboOffsets <= ARRAY_SIZE(data.drawIndexed.uboOffsets));
for (int i = 0; i < numUboOffsets; i++)
data.drawIndexed.uboOffsets[i] = uboOffsets[i];
data.drawIndexed.indexType = indexType;
curRenderStep_->commands.push_back(data);
curRenderStep_->render.numDraws++;
}
@ -417,9 +417,9 @@ public:
// in the debugger.
void DebugAnnotate(const char *annotation) {
_dbg_assert_(curRenderStep_);
VkRenderData data{ VKRRenderCommand::DEBUG_ANNOTATION };
VkRenderData &data = curRenderStep_->commands.push_uninitialized();
data.cmd = VKRRenderCommand::DEBUG_ANNOTATION;
data.debugAnnotation.annotation = annotation;
curRenderStep_->commands.push_back(data);
}
VkCommandBuffer GetInitCmd();
@ -453,8 +453,6 @@ public:
return outOfDateFrames_ > VulkanContext::MAX_INFLIGHT_FRAMES;
}
void Invalidate(InvalidationFlags flags);
void ResetStats();
void DrainCompileQueue();
@ -509,7 +507,7 @@ private:
std::mutex pushMutex_;
std::condition_variable pushCondVar_;
std::queue<VKRRenderThreadTask> renderThreadQueue_;
std::queue<VKRRenderThreadTask *> renderThreadQueue_;
// For readbacks and other reasons we need to sync with the render thread.
std::mutex syncMutex_;

View file

@ -335,8 +335,11 @@ struct DescriptorSetKey {
class VKTexture : public Texture {
public:
VKTexture(VulkanContext *vulkan, VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const TextureDesc &desc)
: vulkan_(vulkan), mipLevels_(desc.mipLevels), format_(desc.format) {}
: vulkan_(vulkan), mipLevels_(desc.mipLevels) {
format_ = desc.format;
}
bool Create(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const TextureDesc &desc);
void Update(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t *const *data, TextureCallback callback, int numLevels);
~VKTexture() {
Destroy();
@ -356,7 +359,13 @@ public:
return VK_NULL_HANDLE; // This would be bad.
}
int NumLevels() const {
return mipLevels_;
}
private:
void UpdateInternal(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t *const *data, TextureCallback callback, int numLevels);
void Destroy() {
if (vkTex_) {
vkTex_->Destroy();
@ -369,8 +378,6 @@ private:
VulkanTexture *vkTex_ = nullptr;
int mipLevels_ = 0;
DataFormat format_ = DataFormat::UNDEFINED;
};
class VKFramebuffer;
@ -421,6 +428,7 @@ public:
Framebuffer *CreateFramebuffer(const FramebufferDesc &desc) override;
void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) override;
void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) override;
void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) override;
bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) override;
@ -430,7 +438,6 @@ public:
// These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override;
void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int layer) override;
void BindCurrentFramebufferForColorInput() override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
@ -476,6 +483,10 @@ public:
void EndFrame() override;
void WipeQueue() override;
int GetFrameCount() override {
return frameCount_;
}
void FlushState() override {}
void ResetStats() override {
@ -520,6 +531,7 @@ private:
VulkanTexture *GetNullTexture();
VulkanContext *vulkan_ = nullptr;
int frameCount_ = 0;
VulkanRenderManager renderManager_;
VulkanTexture *nullTexture_ = nullptr;
@ -684,6 +696,7 @@ VulkanTexture *VKContext::GetNullTexture() {
uint32_t bindOffset;
VkBuffer bindBuf;
uint32_t *data = (uint32_t *)push_->Allocate(w * h * 4, 4, &bindBuf, &bindOffset);
_assert_(data != nullptr);
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
// data[y*w + x] = ((x ^ y) & 1) ? 0xFF808080 : 0xFF000000; // gray/black checkerboard
@ -747,14 +760,14 @@ enum class TextureState {
PENDING_DESTRUCTION,
};
bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *push, const TextureDesc &desc) {
bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const TextureDesc &desc) {
// Zero-sized textures not allowed.
_assert_(desc.width * desc.height * desc.depth > 0); // remember to set depth to 1!
if (desc.width * desc.height * desc.depth <= 0) {
ERROR_LOG(G3D, "Bad texture dimensions %dx%dx%d", desc.width, desc.height, desc.depth);
return false;
}
_assert_(push);
_dbg_assert_(pushBuffer);
format_ = desc.format;
mipLevels_ = desc.mipLevels;
width_ = desc.width;
@ -762,8 +775,6 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *push, const TextureD
depth_ = desc.depth;
vkTex_ = new VulkanTexture(vulkan_, desc.tag);
VkFormat vulkanFormat = DataFormatToVulkan(format_);
int bpp = GetBpp(vulkanFormat);
int bytesPerPixel = bpp / 8;
int usageBits = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
if (mipLevels_ > (int)desc.initData.size()) {
// Gonna have to generate some, which requires TRANSFER_SRC
@ -778,32 +789,10 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *push, const TextureD
}
VkImageLayout layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
if (desc.initData.size()) {
int w = width_;
int h = height_;
int d = depth_;
int i;
for (i = 0; i < (int)desc.initData.size(); i++) {
uint32_t offset;
VkBuffer buf;
size_t size = w * h * d * bytesPerPixel;
if (desc.initDataCallback) {
uint8_t *dest = (uint8_t *)push->Allocate(size, 16, &buf, &offset);
if (!desc.initDataCallback(dest, desc.initData[i], w, h, d, w * bytesPerPixel, h * w * bytesPerPixel)) {
memcpy(dest, desc.initData[i], size);
}
} else {
offset = push->Push((const void *)desc.initData[i], size, 16, &buf);
}
TextureCopyBatch batch;
vkTex_->CopyBufferToMipLevel(cmd, &batch, i, w, h, 0, buf, offset, w);
vkTex_->FinishCopyBatch(cmd, &batch);
w = (w + 1) / 2;
h = (h + 1) / 2;
d = (d + 1) / 2;
}
UpdateInternal(cmd, pushBuffer, desc.initData.data(), desc.initDataCallback, (int)desc.initData.size());
// Generate the rest of the mips automatically.
if (i < mipLevels_) {
vkTex_->GenerateMips(cmd, i, false);
if (desc.initData.size() < mipLevels_) {
vkTex_->GenerateMips(cmd, (int)desc.initData.size(), false);
layout = VK_IMAGE_LAYOUT_GENERAL;
}
}
@ -811,6 +800,44 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushPool *push, const TextureD
return true;
}
void VKTexture::Update(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t * const *data, TextureCallback initDataCallback, int numLevels) {
// Before we can use UpdateInternal, we need to transition the image to the same state as after CreateDirect,
// making it ready for writing.
vkTex_->PrepareForTransferDst(cmd, numLevels);
UpdateInternal(cmd, pushBuffer, data, initDataCallback, numLevels);
vkTex_->RestoreAfterTransferDst(cmd, numLevels);
}
void VKTexture::UpdateInternal(VkCommandBuffer cmd, VulkanPushPool *pushBuffer, const uint8_t * const *data, TextureCallback initDataCallback, int numLevels) {
int w = width_;
int h = height_;
int d = depth_;
int i;
VkFormat vulkanFormat = DataFormatToVulkan(format_);
int bpp = GetBpp(vulkanFormat);
int bytesPerPixel = bpp / 8;
TextureCopyBatch batch;
for (i = 0; i < numLevels; i++) {
uint32_t offset;
VkBuffer buf;
size_t size = w * h * d * bytesPerPixel;
uint8_t *dest = (uint8_t *)pushBuffer->Allocate(size, 16, &buf, &offset);
if (initDataCallback) {
_assert_(dest != nullptr);
if (!initDataCallback(dest, data[i], w, h, d, w * bytesPerPixel, h * w * bytesPerPixel)) {
memcpy(dest, data[i], size);
}
} else {
memcpy(dest, data[i], size);
}
vkTex_->CopyBufferToMipLevel(cmd, &batch, i, w, h, 0, buf, offset, w);
w = (w + 1) / 2;
h = (h + 1) / 2;
d = (d + 1) / 2;
}
vkTex_->FinishCopyBatch(cmd, &batch);
}
static DataFormat DataFormatFromVulkanDepth(VkFormat fmt) {
switch (fmt) {
case VK_FORMAT_D24_UNORM_S8_UINT:
@ -983,9 +1010,9 @@ VKContext::VKContext(VulkanContext *vulkan)
}
}
// Limited, through input attachments and self-dependencies.
// We turn it off here already if buggy.
caps_.framebufferFetchSupported = !bugs_.Has(Bugs::SUBPASS_FEEDBACK_BROKEN);
// Vulkan can support this through input attachments and various extensions, but not worth
// the trouble.
caps_.framebufferFetchSupported = false;
caps_.deviceID = deviceProps.deviceID;
device_ = vulkan->GetDevice();
@ -1082,6 +1109,8 @@ void VKContext::EndFrame() {
// Unbind stuff, to avoid accidentally relying on it across frames (and provide some protection against forgotten unbinds of deleted things).
Invalidate(InvalidationFlags::CACHED_RENDER_STATE);
frameCount_++;
}
void VKContext::Invalidate(InvalidationFlags flags) {
@ -1340,6 +1369,20 @@ Texture *VKContext::CreateTexture(const TextureDesc &desc) {
}
}
void VKContext::UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) {
VkCommandBuffer initCmd = renderManager_.GetInitCmd();
if (!push_ || !initCmd) {
// Too early! Fail.
ERROR_LOG(G3D, "Can't create textures before the first frame has started.");
return;
}
VKTexture *tex = (VKTexture *)texture;
_dbg_assert_(numLevels <= tex->NumLevels());
tex->Update(initCmd, push_, data, initDataCallback, numLevels);
}
static inline void CopySide(VkStencilOpState &dest, const StencilSetup &src) {
dest.compareOp = compToVK[(int)src.compareOp];
dest.failOp = stencilOpToVK[(int)src.failOp];
@ -1495,7 +1538,7 @@ void VKContext::DrawIndexed(int vertexCount, int offset) {
BindCurrentPipeline();
ApplyDynamicState();
renderManager_.DrawIndexed(descSet, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffsets_[0], vulkanIbuf, (int)ibBindOffset + offset * sizeof(uint32_t), vertexCount, 1, VK_INDEX_TYPE_UINT16);
renderManager_.DrawIndexed(descSet, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffsets_[0], vulkanIbuf, (int)ibBindOffset + offset * sizeof(uint32_t), vertexCount, 1);
}
void VKContext::DrawUP(const void *vdata, int vertexCount) {
@ -1505,7 +1548,12 @@ void VKContext::DrawUP(const void *vdata, int vertexCount) {
}
VkBuffer vulkanVbuf, vulkanUBObuf;
size_t vbBindOffset = push_->Push(vdata, vertexCount * curPipeline_->stride[0], 4, &vulkanVbuf);
size_t dataSize = vertexCount * curPipeline_->stride[0];
uint32_t vbBindOffset;
uint8_t *dataPtr = push_->Allocate(dataSize, 4, &vulkanVbuf, &vbBindOffset);
_assert_(dataPtr != nullptr);
memcpy(dataPtr, vdata, dataSize);
uint32_t ubo_offset = (uint32_t)curPipeline_->PushUBO(push_, vulkan_, &vulkanUBObuf);
VkDescriptorSet descSet = GetOrCreateDescriptorSet(vulkanUBObuf);
@ -1566,6 +1614,8 @@ std::vector<std::string> VKContext::GetFeatureList() const {
AddFeature(features, "multiview", vulkan_->GetDeviceFeatures().available.multiview.multiview, vulkan_->GetDeviceFeatures().enabled.multiview.multiview);
AddFeature(features, "multiviewGeometryShader", vulkan_->GetDeviceFeatures().available.multiview.multiviewGeometryShader, vulkan_->GetDeviceFeatures().enabled.multiview.multiviewGeometryShader);
AddFeature(features, "presentId", vulkan_->GetDeviceFeatures().available.presentId.presentId, vulkan_->GetDeviceFeatures().enabled.presentId.presentId);
AddFeature(features, "presentWait", vulkan_->GetDeviceFeatures().available.presentWait.presentWait, vulkan_->GetDeviceFeatures().enabled.presentWait.presentWait);
features.emplace_back(std::string("Preferred depth buffer format: ") + VulkanFormatToString(vulkan_->GetDeviceInfo().preferredDepthStencilFormat));
@ -1728,10 +1778,6 @@ void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChanne
boundImageView_[binding] = renderManager_.BindFramebufferAsTexture(fb->GetFB(), binding, aspect, layer);
}
void VKContext::BindCurrentFramebufferForColorInput() {
renderManager_.BindCurrentFramebufferAsInputAttachment0(VK_IMAGE_ASPECT_COLOR_BIT);
}
void VKContext::GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) {
VKFramebuffer *fb = (VKFramebuffer *)fbo;
if (fb) {

View file

@ -119,7 +119,8 @@ bool DataFormatIsBlockCompressed(DataFormat fmt, int *blockSize) {
}
RefCountedObject::~RefCountedObject() {
_dbg_assert_(refcount_ == 0xDEDEDE);
const int rc = refcount_.load();
_dbg_assert_msg_(rc == 0xDEDEDE, "Unexpected refcount %d in object of type '%s'", rc, name_);
}
bool RefCountedObject::Release() {
@ -131,6 +132,7 @@ bool RefCountedObject::Release() {
return true;
}
} else {
// No point in printing the name here if the object has already been free-d, it'll be corrupt and dangerous to print.
_dbg_assert_msg_(false, "Refcount (%d) invalid for object %p - corrupt?", refcount_.load(), this);
}
return false;
@ -138,11 +140,10 @@ bool RefCountedObject::Release() {
bool RefCountedObject::ReleaseAssertLast() {
bool released = Release();
_dbg_assert_msg_(released, "RefCountedObject: Expected to be the last reference, but isn't!");
_dbg_assert_msg_(released, "RefCountedObject: Expected to be the last reference, but isn't! (%s)", name_);
return released;
}
// ================================== PIXEL/FRAGMENT SHADERS
// The Vulkan ones can be re-used with modern GL later if desired, as they're just GLSL.

View file

@ -359,7 +359,7 @@ protected:
class RefCountedObject {
public:
RefCountedObject() {
explicit RefCountedObject(const char *name) : name_(name) {
refcount_ = 1;
}
RefCountedObject(const RefCountedObject &other) = delete;
@ -372,6 +372,7 @@ public:
private:
std::atomic<int> refcount_;
const char * const name_;
};
template <typename T>
@ -428,18 +429,22 @@ struct AutoRef {
class BlendState : public RefCountedObject {
public:
BlendState() : RefCountedObject("BlendState") {}
};
class SamplerState : public RefCountedObject {
public:
SamplerState() : RefCountedObject("SamplerState") {}
};
class DepthStencilState : public RefCountedObject {
public:
DepthStencilState() : RefCountedObject("DepthStencilState") {}
};
class Framebuffer : public RefCountedObject {
public:
Framebuffer() : RefCountedObject("Framebuffer") {}
int Width() { return width_; }
int Height() { return height_; }
int Layers() { return layers_; }
@ -452,15 +457,20 @@ protected:
class Buffer : public RefCountedObject {
public:
Buffer() : RefCountedObject("Buffer") {}
};
class Texture : public RefCountedObject {
public:
int Width() { return width_; }
int Height() { return height_; }
int Depth() { return depth_; }
Texture() : RefCountedObject("Texture") {}
int Width() const { return width_; }
int Height() const { return height_; }
int Depth() const { return depth_; }
DataFormat Format() const { return format_; }
protected:
int width_ = -1, height_ = -1, depth_ = -1;
DataFormat format_ = DataFormat::UNDEFINED;
};
struct BindingDesc {
@ -480,18 +490,28 @@ struct InputLayoutDesc {
std::vector<AttributeDesc> attributes;
};
class InputLayout : public RefCountedObject { };
class InputLayout : public RefCountedObject {
public:
InputLayout() : RefCountedObject("InputLayout") {}
};
// Uniform types have moved to Shader.h.
class ShaderModule : public RefCountedObject {
public:
ShaderModule() : RefCountedObject("ShaderModule") {}
virtual ShaderStage GetStage() const = 0;
};
class Pipeline : public RefCountedObject { };
class Pipeline : public RefCountedObject {
public:
Pipeline() : RefCountedObject("Pipeline") {}
};
class RasterState : public RefCountedObject {};
class RasterState : public RefCountedObject {
public:
RasterState() : RefCountedObject("RasterState") {}
};
struct StencilSetup {
StencilOp failOp;
@ -713,6 +733,11 @@ public:
// Copies data from the CPU over into the buffer, at a specific offset. This does not change the size of the buffer and cannot write outside it.
virtual void UpdateBuffer(Buffer *buffer, const uint8_t *data, size_t offset, size_t size, UpdateBufferFlags flags) = 0;
// Used to optimize DrawPixels by re-using previously allocated temp textures.
// Do not try to update a texture that might be used by an in-flight command buffer! In OpenGL and D3D, this will cause stalls
// while in Vulkan this might cause various strangeness like image corruption.
virtual void UpdateTextureLevels(Texture *texture, const uint8_t **data, TextureCallback initDataCallback, int numLevels) = 0;
virtual void CopyFramebufferImage(Framebuffer *src, int level, int x, int y, int z, Framebuffer *dst, int dstLevel, int dstX, int dstY, int dstZ, int width, int height, int depth, int channelBits, const char *tag) = 0;
virtual bool BlitFramebuffer(Framebuffer *src, int srcX1, int srcY1, int srcX2, int srcY2, Framebuffer *dst, int dstX1, int dstY1, int dstX2, int dstY2, int channelBits, FBBlitFilter filter, const char *tag) = 0;
@ -816,6 +841,10 @@ public:
// Not very elegant, but more elegant than the old passId hack.
virtual void SetInvalidationCallback(InvalidationCallback callback) = 0;
// Total amount of frames rendered. Unaffected by game pause, so more robust than gpuStats.numFlips
virtual int GetFrameCount() = 0;
virtual int GetFramesInFlight() { return 3; }
protected:
ShaderModule *vsPresets_[VS_MAX_PRESET];
ShaderModule *fsPresets_[FS_MAX_PRESET];

View file

@ -20,7 +20,7 @@ const char *GetDeviceName(int deviceId) {
case DEVICE_ID_PAD_7: return "pad8";
case DEVICE_ID_PAD_8: return "pad9";
case DEVICE_ID_PAD_9: return "pad10";
case DEVICE_ID_XINPUT_0: return "x360"; // keeping these strings for backward compat
case DEVICE_ID_XINPUT_0: return "x360"; // keeping these strings for backward compat. Hm, what would break if we changed them to xbox?
case DEVICE_ID_XINPUT_1: return "x360_2";
case DEVICE_ID_XINPUT_2: return "x360_3";
case DEVICE_ID_XINPUT_3: return "x360_4";
@ -39,7 +39,7 @@ std::vector<InputMapping> confirmKeys;
std::vector<InputMapping> cancelKeys;
std::vector<InputMapping> tabLeftKeys;
std::vector<InputMapping> tabRightKeys;
static std::unordered_map<int, int> uiFlipAnalogY;
static std::unordered_map<InputDeviceID, int> uiFlipAnalogY;
static void AppendKeys(std::vector<InputMapping> &keys, const std::vector<InputMapping> &newKeys) {
for (auto iter = newKeys.begin(); iter != newKeys.end(); ++iter) {
@ -69,11 +69,11 @@ void SetTabLeftRightKeys(const std::vector<InputMapping> &tabLeft, const std::ve
tabRightKeys = tabRight;
}
void SetAnalogFlipY(std::unordered_map<int, int> flipYByDeviceId) {
void SetAnalogFlipY(std::unordered_map<InputDeviceID, int> flipYByDeviceId) {
uiFlipAnalogY = flipYByDeviceId;
}
int GetAnalogYDirection(int deviceId) {
int GetAnalogYDirection(InputDeviceID deviceId) {
auto configured = uiFlipAnalogY.find(deviceId);
if (configured != uiFlipAnalogY.end())
return configured->second;
@ -84,8 +84,8 @@ int GetAnalogYDirection(int deviceId) {
InputMapping InputMapping::FromConfigString(const std::string &str) {
std::vector<std::string> parts;
SplitString(str, '-', parts);
int deviceId = atoi(parts[0].c_str());
int keyCode = atoi(parts[1].c_str());
InputDeviceID deviceId = (InputDeviceID)(atoi(parts[0].c_str()));
InputKeyCode keyCode = (InputKeyCode)atoi(parts[1].c_str());
InputMapping mapping;
mapping.deviceId = deviceId;
@ -94,15 +94,15 @@ InputMapping InputMapping::FromConfigString(const std::string &str) {
}
std::string InputMapping::ToConfigString() const {
return StringFromFormat("%d-%d", deviceId, keyCode);
return StringFromFormat("%d-%d", (int)deviceId, keyCode);
}
void InputMapping::FormatDebug(char *buffer, size_t bufSize) const {
if (IsAxis()) {
int direction;
int axis = Axis(&direction);
snprintf(buffer, bufSize, "Device: %d Axis: %d (%d)", deviceId, axis, direction);
snprintf(buffer, bufSize, "Device: %d Axis: %d (%d)", (int)deviceId, axis, direction);
} else {
snprintf(buffer, bufSize, "Device: %d Key: %d", deviceId, keyCode);
snprintf(buffer, bufSize, "Device: %d Key: %d", (int)deviceId, keyCode);
}
}

View file

@ -12,7 +12,7 @@
// Default device IDs
enum {
enum InputDeviceID {
DEVICE_ID_ANY = -1, // Represents any device ID
DEVICE_ID_DEFAULT = 0, // Old Android
DEVICE_ID_KEYBOARD = 1, // PC keyboard, android keyboards
@ -38,43 +38,15 @@ enum {
DEVICE_ID_TOUCH = 42,
};
inline InputDeviceID operator +(InputDeviceID deviceID, int addend) {
return (InputDeviceID)((int)deviceID + addend);
}
//number of contiguous generic joypad IDs
const int MAX_NUM_PADS = 10;
const char *GetDeviceName(int deviceId);
enum {
PAD_BUTTON_A = 1,
PAD_BUTTON_B = 2,
PAD_BUTTON_X = 4,
PAD_BUTTON_Y = 8,
PAD_BUTTON_LBUMPER = 16,
PAD_BUTTON_RBUMPER = 32,
PAD_BUTTON_START = 64,
PAD_BUTTON_SELECT = 128,
PAD_BUTTON_UP = 256,
PAD_BUTTON_DOWN = 512,
PAD_BUTTON_LEFT = 1024,
PAD_BUTTON_RIGHT = 2048,
PAD_BUTTON_MENU = 4096,
PAD_BUTTON_BACK = 8192,
// For Qt
PAD_BUTTON_JOY_UP = 1<<14,
PAD_BUTTON_JOY_DOWN = 1<<15,
PAD_BUTTON_JOY_LEFT = 1<<16,
PAD_BUTTON_JOY_RIGHT = 1<<17,
PAD_BUTTON_LEFT_THUMB = 1 << 18, // Click left thumb stick on X360
PAD_BUTTON_RIGHT_THUMB = 1 << 19, // Click right thumb stick on X360
PAD_BUTTON_LEFT_TRIGGER = 1 << 21, // Click left thumb stick on X360
PAD_BUTTON_RIGHT_TRIGGER = 1 << 22, // Click left thumb stick on X360
PAD_BUTTON_FASTFORWARD = 1 << 20, // Click Tab to unthrottle
};
#ifndef MAX_KEYQUEUESIZE
#define MAX_KEYQUEUESIZE 20
#endif
@ -98,18 +70,18 @@ private:
return AXIS_BIND_NKCODE_START + axisId * 2 + (direction < 0 ? 1 : 0);
}
public:
InputMapping() : deviceId(0), keyCode(0) {}
InputMapping() : deviceId(DEVICE_ID_DEFAULT), keyCode(0) {}
// From a key mapping
InputMapping(int _deviceId, int key) : deviceId(_deviceId), keyCode(key) {}
InputMapping(InputDeviceID _deviceId, int key) : deviceId(_deviceId), keyCode(key) {}
// From an axis
InputMapping(int _deviceId, int axis, int direction) : deviceId(_deviceId), keyCode(TranslateKeyCodeFromAxis(axis, direction)) {
InputMapping(InputDeviceID _deviceId, int axis, int direction) : deviceId(_deviceId), keyCode(TranslateKeyCodeFromAxis(axis, direction)) {
_dbg_assert_(direction != 0);
}
static InputMapping FromConfigString(const std::string &str);
std::string ToConfigString() const;
int deviceId;
InputDeviceID deviceId;
int keyCode; // Can also represent an axis with direction, if encoded properly.
bool IsAxis() const {
@ -195,15 +167,19 @@ enum {
struct KeyInput {
KeyInput() {}
KeyInput(int devId, int code, int fl) : deviceId(devId), keyCode(code), flags(fl) {}
int deviceId;
int keyCode; // Android keycodes are the canonical keycodes, everyone else map to them.
KeyInput(InputDeviceID devId, InputKeyCode code, int fl) : deviceId(devId), keyCode(code), flags(fl) {}
KeyInput(InputDeviceID devId, int unicode) : deviceId(devId), unicodeChar(unicode), flags(KEY_CHAR) {}
InputDeviceID deviceId;
union {
InputKeyCode keyCode; // Android keycodes are the canonical keycodes, everyone else map to them.
int unicodeChar; // for KEY_CHAR
};
int flags;
};
struct AxisInput {
int deviceId;
int axisId; // Android axis Ids are the canonical ones.
InputDeviceID deviceId;
InputAxis axisId;
float value;
};
@ -219,5 +195,5 @@ void SetConfirmCancelKeys(const std::vector<InputMapping> &confirm, const std::v
void SetTabLeftRightKeys(const std::vector<InputMapping> &tabLeft, const std::vector<InputMapping> &tabRight);
// 0 means unknown (attempt autodetect), -1 means flip, 1 means original direction.
void SetAnalogFlipY(std::unordered_map<int, int> flipYByDeviceId);
int GetAnalogYDirection(int deviceId);
void SetAnalogFlipY(std::unordered_map<InputDeviceID, int> flipYByDeviceId);
int GetAnalogYDirection(InputDeviceID deviceId);

View file

@ -1,6 +1,7 @@
#pragma once
typedef enum _keycode_t {
// These mostly match Android keycodes.
enum InputKeyCode {
NKCODE_BUTTON_CROSS = 23, // trackpad or X button(Xperia Play) is pressed
NKCODE_BUTTON_CROSS_PS3 = 96, // PS3 X button is pressed
NKCODE_BUTTON_CIRCLE = 1004, // Special custom keycode generated from 'O' button by our java code. Or 'O' button if Alt is pressed (TODO)
@ -259,9 +260,10 @@ typedef enum _keycode_t {
NKCODE_EXT_ROTATION_RIGHT = 1114,
NKCODE_MAX
} keycode_t;
};
enum AndroidJoystickAxis {
// These mostly match Android axis IDs.
enum InputAxis {
// Field descriptor #15 I
JOYSTICK_AXIS_X = 0,
JOYSTICK_AXIS_Y = 1,

View file

@ -62,6 +62,9 @@ enum LOG_TYPE {
SCEMISC,
NUMBER_OF_LOGS, // Must be last
// Temporary aliases
ACHIEVEMENTS = HLE, // TODO: Make a real category
};
enum LOG_LEVELS : int {
@ -83,6 +86,7 @@ void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type,
;
bool GenericLogEnabled(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type);
// Exception for Windows - enable more log levels in release mode than on other platforms.
#if defined(_DEBUG) || defined(_WIN32)
#define MAX_LOGLEVEL DEBUG_LEVEL
@ -128,8 +132,8 @@ void SetExtraAssertInfo(const char *info);
#define __FILENAME__ __FILE__
#endif
// If we're in "debug" assert mode
#if MAX_LOGLEVEL >= DEBUG_LEVEL
// If we're a debug build, _dbg_assert_ is active. Not otherwise, even on Windows.
#if defined(_DEBUG)
#define _dbg_assert_(_a_) \
if (!(_a_)) {\

View file

@ -32,6 +32,7 @@ struct SimpleStat {
void Format(char *buffer, size_t sz);
private:
SimpleStat() {}
const char *name_;
// These are initialized in Reset().

View file

@ -1,3 +1,4 @@
#include "Common/StringUtils.h"
#include "expression_parser.h"
#include <ctype.h>
#include <cstring>
@ -14,7 +15,7 @@ typedef enum {
typedef enum { EXCOMM_CONST, EXCOMM_CONST_FLOAT, EXCOMM_REF, EXCOMM_OP } ExpressionCommand;
static char expressionError[512];
static std::string expressionError;
typedef struct {
char Name[4];
@ -219,7 +220,7 @@ bool isAlphaNum(char c)
bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, PostfixExpression& dest)
{
expressionError[0] = 0;
expressionError.clear();
int infixPos = 0;
int infixLen = (int)strlen(infix);
@ -253,7 +254,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
isFloat = true;
else if (parseNumber(subStr,16,subPos,value) == false)
{
snprintf(expressionError, sizeof(expressionError), "Invalid number \"%s\"",subStr);
expressionError = StringFromFormat("Invalid number \"%s\"", subStr);
return false;
}
@ -282,14 +283,14 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
continue;
}
snprintf(expressionError, sizeof(expressionError), "Invalid symbol \"%s\"",subStr);
expressionError = StringFromFormat("Invalid symbol \"%s\"", subStr);
return false;
} else {
int len;
ExpressionOpcodeType type = getExpressionOpcode(&infix[infixPos],len,lastOpcode);
if (type == EXOP_NONE)
{
snprintf(expressionError, sizeof(expressionError), "Invalid operator at \"%s\"",&infix[infixPos]);
expressionError = StringFromFormat("Invalid operator at \"%s\"", &infix[infixPos]);
return false;
}
@ -304,7 +305,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
{
if (opcodeStack.empty())
{
snprintf(expressionError, sizeof(expressionError), "Closing parenthesis without opening one");
expressionError = "Closing parenthesis without opening one";
return false;
}
ExpressionOpcodeType t = opcodeStack[opcodeStack.size()-1];
@ -318,7 +319,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
{
if (opcodeStack.empty())
{
snprintf(expressionError, sizeof(expressionError), "Closing bracket without opening one");
expressionError = "Closing bracket without opening one";
return false;
}
ExpressionOpcodeType t = opcodeStack[opcodeStack.size()-1];
@ -371,7 +372,7 @@ bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, Postf
if (t == EXOP_BRACKETL) // opening bracket without closing one
{
snprintf(expressionError, sizeof(expressionError), "Parenthesis not closed");
expressionError = "Parenthesis not closed";
return false;
}
dest.push_back(ExpressionPair(EXCOMM_OP,t));
@ -430,7 +431,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
opcode = exp[num++].second;
if (valueStack.size() < ExpressionOpcodes[opcode].args)
{
snprintf(expressionError, sizeof(expressionError), "Not enough arguments");
expressionError = "Not enough arguments";
return false;
}
for (int l = 0; l < ExpressionOpcodes[opcode].args; l++)
@ -446,12 +447,12 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
case EXOP_MEMSIZE: // must be followed by EXOP_MEM
if (exp[num++].second != EXOP_MEM)
{
snprintf(expressionError, sizeof(expressionError), "Invalid memsize operator");
expressionError = "Invalid memsize operator";
return false;
}
uint32_t val;
if(funcs->getMemoryValue(arg[1],arg[0],val,expressionError, sizeof(expressionError)) == false)
if (funcs->getMemoryValue(arg[1], arg[0], val, &expressionError) == false)
{
return false;
}
@ -460,7 +461,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
case EXOP_MEM:
{
uint32_t val;
if (funcs->getMemoryValue(arg[0],4,val,expressionError, sizeof(expressionError)) == false)
if (funcs->getMemoryValue(arg[0], 4, val, &expressionError) == false)
{
return false;
}
@ -490,7 +491,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
case EXOP_DIV: // a/b
if (arg[0] == 0)
{
snprintf(expressionError, sizeof(expressionError), "Division by zero");
expressionError = "Division by zero";
return false;
}
if (useFloat)
@ -501,7 +502,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
case EXOP_MOD: // a%b
if (arg[0] == 0)
{
snprintf(expressionError, sizeof(expressionError), "Modulo by zero");
expressionError = "Modulo by zero";
return false;
}
valueStack.push_back(arg[1]%arg[0]);
@ -574,7 +575,7 @@ bool parsePostfixExpression(PostfixExpression& exp, IExpressionFunctions* funcs,
case EXOP_TERTELSE: // exp ? exp : exp, else muss zuerst kommen!
if (exp[num++].second != EXOP_TERTIF)
{
snprintf(expressionError, sizeof(expressionError), "Invalid tertiary operator");
expressionError = "Invalid tertiary operator";
return false;
}
valueStack.push_back(arg[2]?arg[1]:arg[0]);
@ -595,8 +596,9 @@ bool parseExpression(const char *exp, IExpressionFunctions *funcs, uint32_t &des
return parsePostfixExpression(postfix,funcs,dest);
}
const char* getExpressionError()
const char *getExpressionError()
{
if (expressionError[0] == 0) snprintf(expressionError, sizeof(expressionError), "Invalid expression");
return expressionError;
if (expressionError.empty())
expressionError = "Invalid expression";
return expressionError.c_str();
}

View file

@ -2,6 +2,7 @@
#include <cstdint>
#include <cstddef>
#include <string>
#include <vector>
typedef std::pair<uint32_t, uint32_t> ExpressionPair;
@ -21,7 +22,7 @@ public:
virtual bool parseSymbol(char* str, uint32_t& symbolValue) = 0;
virtual uint32_t getReferenceValue(uint32_t referenceIndex) = 0;
virtual ExpressionType getReferenceType(uint32_t referenceIndex) = 0;
virtual bool getMemoryValue(uint32_t address, int size, uint32_t& dest, char *error, size_t errorBufSize) = 0;
virtual bool getMemoryValue(uint32_t address, int size, uint32_t& dest, std::string *error) = 0;
};
bool initPostfixExpression(const char* infix, IExpressionFunctions* funcs, PostfixExpression& dest);

View file

@ -69,9 +69,22 @@ struct Bounds {
Bounds Expand(float xAmount, float yAmount) const {
return Bounds(x - xAmount, y - yAmount, w + xAmount * 2, h + yAmount * 2);
}
Bounds Expand(float left, float top, float right, float bottom) const {
return Bounds(x - left, y - top, w + left + right, h + top + bottom);
}
Bounds Offset(float xAmount, float yAmount) const {
return Bounds(x + xAmount, y + yAmount, w, h);
}
Bounds Inset(float left, float top, float right, float bottom) {
return Bounds(x + left, y + top, w - left - right, h - bottom - top);
}
Bounds Inset(float xAmount, float yAmount) const {
return Bounds(x + xAmount, y + yAmount, w - xAmount * 2, h - yAmount * 2);
}
Bounds Inset(float left, float top, float right, float bottom) const {
return Bounds(x + left, y + top, w - left - right, h - top - bottom);
}
float x;
float y;

View file

@ -67,6 +67,13 @@ inline T clamp_value(T val, T floor, T cap) {
return val;
}
// Very common operation, familiar from shaders.
inline float saturatef(float x) {
if (x > 1.0f) return 1.0f;
else if (x < 0.0f) return 0.0f;
else return x;
}
#define ROUND_UP(x, a) (((x) + (a) - 1) & ~((a) - 1))
#define ROUND_DOWN(x, a) ((x) & ~((a) - 1))

View file

@ -39,7 +39,7 @@ public:
bool GrabMemSpace(size_t size);
void ReleaseSpace();
void *CreateView(s64 offset, size_t size, void *base = 0);
void ReleaseView(void *view, size_t size);
void ReleaseView(s64 offset, void *view, size_t size);
// This only finds 1 GB in 32-bit
u8 *Find4GBBase();

View file

@ -129,7 +129,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base) {
return retval;
}
void MemArena::ReleaseView(void* view, size_t size) {
void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
munmap(view, size);
}

View file

@ -79,7 +79,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base) {
return (void *)target;
}
void MemArena::ReleaseView(void* view, size_t size) {
void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
vm_address_t addr = (vm_address_t)view;
vm_deallocate(mach_task_self(), addr, size);
}

View file

@ -0,0 +1,83 @@
// Copyright (C) 2023 M4xw
// This program 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, version 2.0 or later versions.
// This program 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
#include "ppsspp_config.h"
#if PPSSPP_PLATFORM(SWITCH)
#include <stdio.h>
#include <malloc.h> // memalign
#include <switch.h>
#include "Common/MemArena.h"
static uintptr_t memoryBase = 0;
static uintptr_t memoryCodeBase = 0;
static uintptr_t memorySrcBase = 0;
size_t MemArena::roundup(size_t x) {
return x;
}
bool MemArena::NeedsProbing() {
return false;
}
bool MemArena::GrabMemSpace(size_t size) {
return true;
}
void MemArena::ReleaseSpace() {
if (R_FAILED(svcUnmapProcessCodeMemory(envGetOwnProcessHandle(), (u64)memoryCodeBase, (u64)memorySrcBase, 0x10000000)))
printf("Failed to release view space...\n");
free((void *)memorySrcBase);
memorySrcBase = 0;
}
void *MemArena::CreateView(s64 offset, size_t size, void *base) {
Result rc = svcMapProcessMemory(base, envGetOwnProcessHandle(), (u64)(memoryCodeBase + offset), size);
if (R_FAILED(rc)) {
printf("Fatal error creating the view... base: %p offset: %p size: %p src: %p err: %d\n",
(void *)base, (void *)offset, (void *)size, (void *)(memoryCodeBase + offset), rc);
} else {
printf("Created the view... base: %p offset: %p size: %p src: %p err: %d\n",
(void *)base, (void *)offset, (void *)size, (void *)(memoryCodeBase + offset), rc);
}
return base;
}
void MemArena::ReleaseView(s64 offset, void *view, size_t size) {
if (R_FAILED(svcUnmapProcessMemory(view, envGetOwnProcessHandle(), (u64)(memoryCodeBase + offset), size)))
printf("Failed to unmap view...\n");
}
u8 *MemArena::Find4GBBase() {
memorySrcBase = (uintptr_t)memalign(0x1000, 0x10000000);
if (!memoryBase)
memoryBase = (uintptr_t)virtmemReserve(0x10000000);
if (!memoryCodeBase)
memoryCodeBase = (uintptr_t)virtmemReserve(0x10000000);
if (R_FAILED(svcMapProcessCodeMemory(envGetOwnProcessHandle(), (u64)memoryCodeBase, (u64)memorySrcBase, 0x10000000)))
printf("Failed to map memory...\n");
if (R_FAILED(svcSetProcessMemoryPermission(envGetOwnProcessHandle(), memoryCodeBase, 0x10000000, Perm_Rx)))
printf("Failed to set perms...\n");
return (u8 *)memoryBase;
}
#endif // PPSSPP_PLATFORM(SWITCH)

View file

@ -17,7 +17,7 @@
#include "ppsspp_config.h"
#if !defined(_WIN32) && !defined(ANDROID) && !defined(__APPLE__)
#if !defined(_WIN32) && !defined(ANDROID) && !defined(__APPLE__) && !PPSSPP_PLATFORM(SWITCH)
#include <sys/mman.h>
#include <sys/stat.h>
@ -121,7 +121,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base)
return retval;
}
void MemArena::ReleaseView(void* view, size_t size) {
void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
munmap(view, size);
}

View file

@ -54,7 +54,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *viewbase) {
return ptr;
}
void MemArena::ReleaseView(void* view, size_t size) {
void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
#if PPSSPP_PLATFORM(UWP)
#else
UnmapViewOfFile(view);

View file

@ -17,6 +17,7 @@
#include "ppsspp_config.h"
#if !PPSSPP_PLATFORM(SWITCH)
#include <cstring>
#include <cstdlib>
@ -257,7 +258,7 @@ void *AllocateAlignedMemory(size_t size, size_t alignment) {
#endif
#endif
_assert_msg_(ptr != nullptr, "Failed to allocate aligned memory of size %llu", size);
_assert_msg_(ptr != nullptr, "Failed to allocate aligned memory of size %llu", (unsigned long long)size);
return ptr;
}
@ -355,3 +356,4 @@ int GetMemoryProtectPageSize() {
#endif
return MEM_PAGE_SIZE;
}
#endif // !PPSSPP_PLATFORM(SWITCH)

View file

@ -18,7 +18,11 @@
#pragma once
#ifndef _WIN32
#ifndef __SWITCH__
#include <sys/mman.h>
#else
#include <switch.h>
#endif // !__SWITCH__
#endif
#include <stdint.h>

View file

@ -0,0 +1,89 @@
// Copyright (C) 2003 Dolphin Project.
// This program 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, version 2.0 or later versions.
// This program 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "ppsspp_config.h"
#if PPSSPP_PLATFORM(SWITCH)
#include <cstring>
#include <cstdlib>
#include "Common/CommonTypes.h"
#include "Common/Log.h"
#include "Common/MemoryUtil.h"
#include "Common/StringUtils.h"
#include "Common/SysError.h"
#include <errno.h>
#include <stdio.h>
#include <malloc.h> // memalign
#define MEM_PAGE_SIZE (0x1000)
#define MEM_PAGE_MASK ((MEM_PAGE_SIZE)-1)
#define ppsspp_round_page(x) ((((uintptr_t)(x)) + MEM_PAGE_MASK) & ~(MEM_PAGE_MASK))
// On Switch we dont support allocating executable memory here
// See CodeBlock.h
void *AllocateExecutableMemory(size_t size) {
return nullptr;
}
void *AllocateMemoryPages(size_t size, uint32_t memProtFlags) {
void* ptr = nullptr;
size = ppsspp_round_page(size);
ptr = memalign(MEM_PAGE_SIZE, size);
return ptr;
}
void *AllocateAlignedMemory(size_t size, size_t alignment) {
void* ptr = memalign(alignment, size);
_assert_msg_(ptr != nullptr, "Failed to allocate aligned memory of size %lu", size);
return ptr;
}
void FreeMemoryPages(void *ptr, size_t size) {
if (!ptr)
return;
free(ptr);
return;
}
void FreeExecutableMemory(void *ptr, size_t size) {
return; // Not supported on Switch
}
void FreeAlignedMemory(void* ptr) {
if (!ptr)
return;
free(ptr);
}
bool PlatformIsWXExclusive() {
return false; // Switch technically is W^X but we use dual mappings instead of reprotecting the pages to allow a W and X mapping
}
bool ProtectMemoryPages(const void* ptr, size_t size, uint32_t memProtFlags) {
return true;
}
int GetMemoryProtectPageSize() {
return MEM_PAGE_SIZE;
}
#endif // PPSSPP_PLATFORM(SWITCH)

View file

@ -180,7 +180,7 @@ void Connection::Disconnect() {
namespace http {
// TODO: do something sane here
constexpr const char *DEFAULT_USERAGENT = "NATIVEAPP 1.0";
constexpr const char *DEFAULT_USERAGENT = "PPSSPP";
Client::Client() {
httpVersion_ = "1.1";
@ -458,8 +458,8 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::stri
return 0;
}
Download::Download(const std::string &url, const Path &outfile)
: progress_(&cancelled_), url_(url), outfile_(outfile) {
Download::Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile)
: method_(method), progress_(&cancelled_), url_(url), postData_(postData), postMime_(postMime), outfile_(outfile) {
}
Download::~Download() {
@ -484,13 +484,17 @@ void Download::SetFailed(int code) {
completed_ = true;
}
int Download::PerformGET(const std::string &url) {
int Download::Perform(const std::string &url) {
Url fileUrl(url);
if (!fileUrl.Valid()) {
return -1;
}
http::Client client;
if (!userAgent_.empty()) {
client.SetUserAgent(userAgent_);
}
if (!client.Resolve(fileUrl.Host().c_str(), fileUrl.Port())) {
ERROR_LOG(IO, "Failed resolving %s", url.c_str());
return -1;
@ -510,7 +514,11 @@ int Download::PerformGET(const std::string &url) {
}
RequestParams req(fileUrl.Resource(), acceptMime_);
if (method_ == RequestMethod::GET) {
return client.GET(req, &buffer_, responseHeaders_, &progress_);
} else {
return client.POST(req, postData_, postMime_, &buffer_, &progress_);
}
}
std::string Download::RedirectLocation(const std::string &baseUrl) {
@ -533,7 +541,7 @@ void Download::Do() {
std::string downloadURL = url_;
while (resultCode_ == 0) {
int resultCode = PerformGET(downloadURL);
int resultCode = Perform(downloadURL);
if (resultCode == -1) {
SetFailed(resultCode);
return;
@ -557,12 +565,12 @@ void Download::Do() {
}
if (resultCode == 200) {
INFO_LOG(IO, "Completed downloading %s to %s", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str());
INFO_LOG(IO, "Completed requesting %s (storing result to %s)", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str());
if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) {
ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str());
}
} else {
ERROR_LOG(IO, "Error downloading '%s' to '%s': %i", url_.c_str(), outfile_.c_str(), resultCode);
ERROR_LOG(IO, "Error requesting '%s' (storing result to '%s'): %i", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str(), resultCode);
}
resultCode_ = resultCode;
}
@ -575,10 +583,12 @@ void Download::Do() {
}
std::shared_ptr<Download> Downloader::StartDownload(const std::string &url, const Path &outfile, const char *acceptMime) {
std::shared_ptr<Download> dl(new Download(url, outfile));
std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile));
if (!userAgent_.empty())
dl->SetUserAgent(userAgent_);
if (acceptMime)
dl->SetAccept(acceptMime);
downloads_.push_back(dl);
newDownloads_.push_back(dl);
dl->Start();
return dl;
}
@ -588,19 +598,40 @@ std::shared_ptr<Download> Downloader::StartDownloadWithCallback(
const Path &outfile,
std::function<void(Download &)> callback,
const char *acceptMime) {
std::shared_ptr<Download> dl(new Download(url, outfile));
std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile));
if (!userAgent_.empty())
dl->SetUserAgent(userAgent_);
if (acceptMime)
dl->SetAccept(acceptMime);
dl->SetCallback(callback);
downloads_.push_back(dl);
newDownloads_.push_back(dl);
dl->Start();
return dl;
}
std::shared_ptr<Download> Downloader::AsyncPostWithCallback(
const std::string &url,
const std::string &postData,
const std::string &postMime,
std::function<void(Download &)> callback) {
std::shared_ptr<Download> dl(new Download(RequestMethod::POST, url, postData, postMime, Path()));
if (!userAgent_.empty())
dl->SetUserAgent(userAgent_);
dl->SetCallback(callback);
newDownloads_.push_back(dl);
dl->Start();
return dl;
}
void Downloader::Update() {
for (auto iter : newDownloads_) {
downloads_.push_back(iter);
}
newDownloads_.clear();
restart:
for (size_t i = 0; i < downloads_.size(); i++) {
auto &dl = downloads_[i];
auto dl = downloads_[i];
if (dl->Done()) {
dl->RunCallback();
dl->Join();
@ -610,6 +641,14 @@ void Downloader::Update() {
}
}
void Downloader::WaitForAll() {
// TODO: Should lock? Though, OK if called from main thread, where Update() is called from.
while (!downloads_.empty()) {
Update();
sleep_ms(10);
}
}
std::vector<float> Downloader::GetCurrentProgress() {
std::vector<float> progress;
for (size_t i = 0; i < downloads_.size(); i++) {

View file

@ -97,10 +97,15 @@ protected:
double dataTimeout_ = 900.0;
};
// Not particularly efficient, but hey - it's a background download, that's pretty cool :P
enum class RequestMethod {
GET,
POST,
};
// Really an asynchronous request.
class Download {
public:
Download(const std::string &url, const Path &outfile);
Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile);
~Download();
void Start();
@ -151,20 +156,27 @@ public:
// Downloader::GetCurrentProgress won't return it in the results.
bool IsHidden() const { return hidden_; }
void SetHidden(bool hidden) { hidden_ = hidden; }
void SetUserAgent(const std::string &userAgent) {
userAgent_ = userAgent;
}
private:
void Do(); // Actually does the download. Runs on thread.
int PerformGET(const std::string &url);
int Perform(const std::string &url);
std::string RedirectLocation(const std::string &baseUrl);
void SetFailed(int code);
RequestProgress progress_;
RequestMethod method_;
std::string postData_;
std::string userAgent_;
Buffer buffer_;
std::vector<std::string> responseHeaders_;
std::string url_;
Path outfile_;
std::thread thread_;
const char *acceptMime_ = "*/*";
std::string postMime_;
int resultCode_ = 0;
bool completed_ = false;
bool failed_ = false;
@ -190,14 +202,34 @@ public:
std::function<void(Download &)> callback,
const char *acceptMime = nullptr);
std::shared_ptr<Download> AsyncPostWithCallback(
const std::string &url,
const std::string &postData,
const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder.
std::function<void(Download &)> callback);
// Drops finished downloads from the list.
void Update();
void CancelAll();
void WaitForAll();
void SetUserAgent(const std::string &userAgent) {
userAgent_ = userAgent;
}
std::vector<float> GetCurrentProgress();
size_t GetActiveCount() const {
return downloads_.size();
}
private:
std::vector<std::shared_ptr<Download>> downloads_;
// These get copied to downloads_ in Update(). It's so that callbacks can add new downloads
// while running.
std::vector<std::shared_ptr<Download>> newDownloads_;
std::string userAgent_;
};
} // http

View file

@ -47,7 +47,7 @@ bool TextDrawerAndroid::IsReady() const {
uint32_t TextDrawerAndroid::SetFont(const char *fontName, int size, int flags) {
// We will only use the default font but just for consistency let's still involve
// the font name.
uint32_t fontHash = hash::Adler32((const uint8_t *)fontName, strlen(fontName));
uint32_t fontHash = fontName ? hash::Adler32((const uint8_t *)fontName, strlen(fontName)) : 1337;
fontHash ^= size;
fontHash ^= flags << 10;

View file

@ -134,13 +134,25 @@ void TextDrawerWin32::MeasureString(const char *str, size_t len, float *w, float
SelectObject(ctx_->hDC, iter->second->hFont);
}
std::string toMeasure = ReplaceAll(std::string(str, len), "&&", "&");
std::vector<std::string> lines;
SplitString(toMeasure, '\n', lines);
int extW = 0, extH = 0;
for (auto &line : lines) {
SIZE size;
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(ReplaceAll(std::string(str, len), "\n", "\r\n"), "&&", "&"));
std::wstring wstr = ConvertUTF8ToWString(line);
GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size);
if (size.cx > extW)
extW = size.cx;
extH += size.cy;
}
entry = new TextMeasureEntry();
entry->width = size.cx;
entry->height = size.cy;
entry->width = extW;
entry->height = extH;
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
}

View file

@ -209,8 +209,8 @@ void CPUInfo::Detect()
RiscV_C = ExtensionSupported(hwcap, 'C');
RiscV_V = ExtensionSupported(hwcap, 'V');
RiscV_B = ExtensionSupported(hwcap, 'B');
// Let's assume for now...
RiscV_Zicsr = RiscV_M && RiscV_A && RiscV_F && RiscV_D;
// We assume as in RVA20U64 that F means Zicsr is available.
RiscV_Zicsr = RiscV_F;
#ifdef USE_CPU_FEATURES
cpu_features::RiscvInfo info = cpu_features::GetRiscvInfo();
@ -220,6 +220,7 @@ void CPUInfo::Detect()
RiscV_F = info.features.F;
RiscV_D = info.features.D;
RiscV_C = info.features.C;
RiscV_V = info.features.V;
RiscV_Zicsr = info.features.Zicsr;
truncate_cpy(brand_string, info.uarch);

View file

@ -302,6 +302,12 @@ public:
void NEG(RiscVReg rd, RiscVReg rs) {
SUB(rd, R_ZERO, rs);
}
void SNEZ(RiscVReg rd, RiscVReg rs) {
SLTU(rd, R_ZERO, rs);
}
void SEQZ(RiscVReg rd, RiscVReg rs) {
SLTIU(rd, rs, 1);
}
void FENCE(Fence predecessor, Fence successor);
void FENCE_TSO();
@ -896,6 +902,9 @@ public:
void MINU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2);
void SEXT_B(RiscVReg rd, RiscVReg rs);
void SEXT_H(RiscVReg rd, RiscVReg rs);
void SEXT_W(RiscVReg rd, RiscVReg rs) {
ADDIW(rd, rs, 0);
}
void ZEXT_H(RiscVReg rd, RiscVReg rs);
void ZEXT_W(RiscVReg rd, RiscVReg rs) {
ADD_UW(rd, rs, R_ZERO);

View file

@ -147,6 +147,11 @@ public:
void DoMarker(const char *prevName, u32 arbitraryNumber = 0x42);
void SkipBytes(size_t bytes) {
// Should work in all modes.
*ptr += bytes;
}
size_t Offset() const { return *ptr - ptrStart_; }
private:

View file

@ -368,3 +368,31 @@ std::string UnescapeMenuString(const char *input, char *shortcutChar) {
}
return output;
}
std::string ApplySafeSubstitutions(const char *format, const std::string &string1, const std::string &string2, const std::string &string3) {
size_t formatLen = strlen(format);
std::string output;
output.reserve(formatLen + 20);
for (size_t i = 0; i < formatLen; i++) {
char c = format[i];
if (c != '%') {
output.push_back(c);
continue;
}
if (i >= formatLen - 1) {
break;
}
switch (format[i + 1]) {
case '1':
output += string1; i++;
break;
case '2':
output += string2; i++;
break;
case '3':
output += string3; i++;
break;
}
}
return output;
}

View file

@ -111,3 +111,7 @@ inline void CharArrayFromFormat(char (& out)[Count], const char* format, ...)
// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"
bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, std::string* _pExtension);
// Replaces %1, %2, %3 in format with arg1, arg2, arg3.
// Much safer than snprintf and friends.
std::string ApplySafeSubstitutions(const char *format, const std::string &string1, const std::string &string2 = "", const std::string &string3 = "");

View file

@ -20,9 +20,6 @@ class GraphicsContext;
// This might get called multiple times in some implementations, you must be able to handle that.
void NativeGetAppInfo(std::string *app_dir_name, std::string *app_nice_name, bool *landscape, std::string *version);
// Generic host->C++ messaging, used for functionality like system-native popup input boxes.
void NativeMessageReceived(const char *message, const char *value);
// Easy way for the Java side to ask the C++ side for configuration options, such as
// the rotation lock which must be controlled from Java on Android.
// It is currently not called on non-Android platforms.
@ -87,3 +84,6 @@ void NativeShutdownGraphics();
void NativeShutdown();
void PostLoadConfig();
void NativeSaveSecret(const char *nameOfSecret, const std::string &data);
std::string NativeLoadSecret(const char *nameOfSecret);

281
Common/System/OSD.cpp Normal file
View file

@ -0,0 +1,281 @@
#include <cstring>
#include "Common/System/OSD.h"
#include "Common/TimeUtil.h"
#include "Common/Log.h"
#include "Common/Math/math_util.h"
OnScreenDisplay g_OSD;
// Effectively forever.
constexpr double forever_s = 10000000000.0;
void OnScreenDisplay::Update() {
std::lock_guard<std::mutex> guard(mutex_);
double now = time_now_d();
for (auto iter = entries_.begin(); iter != entries_.end(); ) {
if (now >= iter->endTime) {
iter = entries_.erase(iter);
} else {
iter++;
}
}
for (auto iter = sideEntries_.begin(); iter != sideEntries_.end(); ) {
if (now >= iter->endTime) {
iter = sideEntries_.erase(iter);
} else {
iter++;
}
}
for (auto iter = bars_.begin(); iter != bars_.end(); ) {
if (now >= iter->endTime) {
iter = bars_.erase(iter);
} else {
iter++;
}
}
}
std::vector<OnScreenDisplay::Entry> OnScreenDisplay::Entries() {
std::lock_guard<std::mutex> guard(mutex_);
return entries_; // makes a copy.
}
std::vector<OnScreenDisplay::Entry> OnScreenDisplay::SideEntries() {
std::lock_guard<std::mutex> guard(mutex_);
return sideEntries_; // makes a copy.
}
std::vector<OnScreenDisplay::ProgressBar> OnScreenDisplay::ProgressBars() {
std::lock_guard<std::mutex> guard(mutex_);
return bars_; // makes a copy.
}
void OnScreenDisplay::NudgeSidebar() {
sideBarShowTime_ = time_now_d();
}
float OnScreenDisplay::SidebarAlpha() const {
double timeSinceNudge = time_now_d() - sideBarShowTime_;
// Fade out in 1/4 second, 0.1s after the last nudge.
return saturatef(1.0f - ((float)timeSinceNudge - 0.1f) * 4.0f);
}
void OnScreenDisplay::Show(OSDType type, const std::string &text, const std::string &text2, const std::string &icon, float duration_s, const char *id) {
// Automatic duration based on type.
if (duration_s <= 0.0f) {
switch (type) {
case OSDType::MESSAGE_ERROR:
case OSDType::MESSAGE_WARNING:
duration_s = 4.0f;
break;
case OSDType::MESSAGE_FILE_LINK:
duration_s = 5.0f;
break;
case OSDType::MESSAGE_SUCCESS:
duration_s = 2.0f;
break;
default:
duration_s = 1.5f;
break;
}
}
double now = time_now_d();
std::lock_guard<std::mutex> guard(mutex_);
if (id) {
for (auto iter = entries_.begin(); iter != entries_.end(); ++iter) {
if (iter->id && !strcmp(iter->id, id)) {
Entry msg = *iter;
msg.endTime = now + duration_s;
msg.text = text;
msg.text2 = text2;
msg.type = type;
msg.iconName = icon;
// Move to top (should we? maybe not?)
entries_.erase(iter);
entries_.insert(entries_.begin(), msg);
return;
}
}
}
Entry msg;
msg.text = text;
msg.text2 = text2;
msg.iconName = icon;
msg.startTime = now;
msg.endTime = now + duration_s;
msg.type = type;
msg.id = id;
entries_.insert(entries_.begin(), msg);
}
void OnScreenDisplay::ShowAchievementUnlocked(int achievementID) {
double now = time_now_d();
double duration_s = 5.0;
Entry msg;
msg.numericID = achievementID;
msg.type = OSDType::ACHIEVEMENT_UNLOCKED;
msg.startTime = now;
msg.endTime = now + duration_s;
entries_.insert(entries_.begin(), msg);
}
void OnScreenDisplay::ShowAchievementProgress(int achievementID, float duration_s) {
double now = time_now_d();
for (auto &entry : sideEntries_) {
if (entry.numericID == achievementID && entry.type == OSDType::ACHIEVEMENT_PROGRESS) {
// Duplicate, let's just bump the timer.
entry.startTime = now;
entry.endTime = now + (double)duration_s;
// We're done.
return;
}
}
// OK, let's make a new side-entry.
Entry entry;
entry.numericID = achievementID;
entry.type = OSDType::ACHIEVEMENT_PROGRESS;
entry.startTime = now;
entry.endTime = now + (double)duration_s;
sideEntries_.insert(sideEntries_.begin(), entry);
}
void OnScreenDisplay::ShowChallengeIndicator(int achievementID, bool show) {
double now = time_now_d();
for (auto &entry : sideEntries_) {
if (entry.numericID == achievementID && entry.type == OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR && !show) {
// Hide and eventually delete it.
entry.endTime = now + (double)FadeoutTime();
// Found it, we're done.
return;
}
}
if (!show) {
// Sanity check
return;
}
// OK, let's make a new side-entry.
Entry entry;
entry.numericID = achievementID;
entry.type = OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR;
entry.startTime = now;
entry.endTime = now + forever_s;
sideEntries_.insert(sideEntries_.begin(), entry);
}
void OnScreenDisplay::ShowLeaderboardTracker(int leaderboardTrackerID, const char *trackerText, bool show) { // show=true is used both for create and update.
double now = time_now_d();
for (auto &entry : sideEntries_) {
if (entry.numericID == leaderboardTrackerID && entry.type == OSDType::LEADERBOARD_TRACKER) {
if (show) {
// Just an update.
entry.text = trackerText ? trackerText : "";
} else {
// Keep the current text, hide and eventually delete it.
entry.endTime = now + (double)FadeoutTime();
}
// Found it, we're done.
return;
}
}
if (!show) {
// Sanity check
return;
}
// OK, let's make a new side-entry.
Entry entry;
entry.numericID = leaderboardTrackerID;
entry.type = OSDType::LEADERBOARD_TRACKER;
entry.startTime = now;
entry.endTime = now + forever_s;
if (trackerText) {
entry.text = trackerText;
}
sideEntries_.insert(sideEntries_.begin(), entry);
}
void OnScreenDisplay::ShowOnOff(const std::string &message, bool on, float duration_s) {
// TODO: translate "on" and "off"? Or just get rid of this whole thing?
Show(OSDType::MESSAGE_INFO, message + ": " + (on ? "on" : "off"), duration_s);
}
void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress) {
std::lock_guard<std::mutex> guard(mutex_);
double now = time_now_d();
bool found = false;
for (auto &bar : bars_) {
if (bar.id == id) {
bar.minValue = minValue;
bar.maxValue = maxValue;
bar.progress = progress;
bar.message = message;
bar.endTime = now + 60.0; // Nudge the progress bar to keep it shown.
return;
}
}
ProgressBar bar;
bar.id = id;
bar.message = std::move(message);
bar.minValue = minValue;
bar.maxValue = maxValue;
bar.progress = progress;
bar.endTime = now + 60.0; // Show the progress bar for 60 seconds, then fade it out.
bars_.push_back(bar);
}
void OnScreenDisplay::RemoveProgressBar(std::string id) {
std::lock_guard<std::mutex> guard(mutex_);
for (auto iter = bars_.begin(); iter != bars_.end(); iter++) {
if (iter->id == id) {
iter->progress = iter->maxValue;
iter->endTime = time_now_d() + FadeoutTime();
break;
}
}
}
// Fades out everything related to achievements. Should be used on game shutdown.
void OnScreenDisplay::ClearAchievementStuff() {
double now = time_now_d();
for (auto &iter : entries_) {
switch (iter.type) {
case OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR:
case OSDType::ACHIEVEMENT_UNLOCKED:
case OSDType::ACHIEVEMENT_PROGRESS:
case OSDType::LEADERBOARD_TRACKER:
iter.endTime = now;
break;
default:
break;
}
}
for (auto &iter : sideEntries_) {
switch (iter.type) {
case OSDType::ACHIEVEMENT_CHALLENGE_INDICATOR:
case OSDType::ACHIEVEMENT_UNLOCKED:
case OSDType::ACHIEVEMENT_PROGRESS:
case OSDType::LEADERBOARD_TRACKER:
iter.endTime = now;
break;
default:
break;
}
}
}

98
Common/System/OSD.h Normal file
View file

@ -0,0 +1,98 @@
#pragma once
#include <string>
#include <vector>
#include <mutex>
// Shows a visible message to the user.
// The default implementation in NativeApp.cpp uses our "osm" system (on screen messaging).
enum class OSDType {
MESSAGE_INFO,
MESSAGE_SUCCESS,
MESSAGE_WARNING,
MESSAGE_ERROR,
MESSAGE_ERROR_DUMP, // displays lots of text (after the first line), small size
MESSAGE_FILE_LINK,
ACHIEVEMENT_UNLOCKED,
// Side entries
ACHIEVEMENT_PROGRESS, // Achievement icon + "measured_progress" text, auto-hide after 2s
ACHIEVEMENT_CHALLENGE_INDICATOR, // Achievement icon ONLY, no auto-hide
LEADERBOARD_TRACKER,
};
// Data holder for on-screen messages.
class OnScreenDisplay {
public:
// If you specify 0.0f as duration, a duration will be chosen automatically depending on type.
void Show(OSDType type, const std::string &text, float duration_s = 0.0f, const char *id = nullptr) {
Show(type, text, "", duration_s, id);
}
void Show(OSDType type, const std::string &text, const std::string &text2, float duration_s = 0.0f, const char *id = nullptr) {
Show(type, text, text2, "", duration_s, id);
}
void Show(OSDType type, const std::string &text, const std::string &text2, const std::string &icon, float duration_s = 0.0f, const char *id = nullptr);
void ShowOnOff(const std::string &message, bool on, float duration_s = 0.0f);
bool IsEmpty() const { return entries_.empty(); } // Shortcut to skip rendering.
// Call this every frame, cleans up old entries.
void Update();
// Specialized achievement-related types. These go to the side notifications, not the top-middle.
void ShowAchievementUnlocked(int achievementID);
void ShowAchievementProgress(int achievementID, float duration_s);
void ShowChallengeIndicator(int achievementID, bool show); // call with show=false to hide.
void ShowLeaderboardTracker(int leaderboardTrackerID, const char *trackerText, bool show); // show=true is used both for create and update.
// Progress bar controls
// Set is both create and update. If you set maxValue <= minValue, you'll create an "indeterminate" progress
// bar that doesn't show a specific amount of progress.
void SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress);
void RemoveProgressBar(std::string id);
// Call every frame to keep the sidebar visible. Otherwise it'll fade out.
void NudgeSidebar();
float SidebarAlpha() const;
// Fades out everything related to achievements. Should be used on game shutdown.
void ClearAchievementStuff();
struct Entry {
OSDType type;
std::string text;
std::string text2;
std::string iconName;
int numericID;
const char *id;
double startTime;
double endTime;
};
struct ProgressBar {
std::string id;
std::string message;
int minValue;
int maxValue;
int progress;
double endTime;
};
std::vector<Entry> Entries();
std::vector<Entry> SideEntries();
std::vector<ProgressBar> ProgressBars();
static float FadeoutTime() { return 0.25f; }
private:
std::vector<Entry> entries_;
std::vector<Entry> sideEntries_;
std::vector<ProgressBar> bars_;
std::mutex mutex_;
double sideBarShowTime_ = 0.0;
};
extern OnScreenDisplay g_OSD;

View file

@ -1,7 +1,10 @@
#include <cstring>
#include "Common/System/Request.h"
#include "Common/System/System.h"
#include "Common/Log.h"
#include "Common/File/Path.h"
#include "Common/TimeUtil.h"
RequestManager g_requestManager;

View file

@ -61,7 +61,8 @@ private:
RequestFailedCallback callback;
};
int idCounter_ = 0;
// Let's start at 10 to get a recognizably valid ID in logs.
int idCounter_ = 10;
std::vector<PendingSuccess> pendingSuccesses_;
std::vector<PendingFailure> pendingFailures_;
std::mutex responseMutex_;
@ -72,12 +73,13 @@ const char *RequestTypeAsString(SystemRequestType type);
extern RequestManager g_requestManager;
// Wrappers for easy requests.
// NOTE: Semantics have changed - this no longer calls the callback on cancellation.
// NOTE: Semantics have changed - this no longer calls the callback on cancellation, instead you
// can specify a different callback for that.
inline void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) {
g_requestManager.MakeSystemRequest(SystemRequestType::INPUT_TEXT_MODAL, callback, failedCallback, title, defaultValue, 0);
}
// This one will pop up a special image brwoser if available. You can also pick
// This one will pop up a special image browser if available. You can also pick
// images with the file browser below.
inline void System_BrowseForImage(const std::string &title, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) {
g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_IMAGE, callback, failedCallback, title, "", 0);
@ -88,6 +90,7 @@ enum class BrowseFileType {
IMAGE,
INI,
DB,
SOUND_EFFECT,
ANY,
};
@ -99,6 +102,11 @@ inline void System_BrowseForFolder(const std::string &title, RequestCallback cal
g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_FOLDER, callback, failedCallback, title, "", 0);
}
// The returned string is username + '\n' + password.
inline void System_AskUsernamePassword(const std::string &title, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) {
g_requestManager.MakeSystemRequest(SystemRequestType::ASK_USERNAME_PASSWORD, callback, failedCallback, title, "", 0);
}
inline void System_CopyStringToClipboard(const std::string &string) {
g_requestManager.MakeSystemRequest(SystemRequestType::COPY_TO_CLIPBOARD, nullptr, nullptr, string, "", 0);
}
@ -111,6 +119,10 @@ inline void System_RestartApp(const std::string &params) {
g_requestManager.MakeSystemRequest(SystemRequestType::RESTART_APP, nullptr, nullptr, params, "", 0);
}
inline void System_RecreateActivity() {
g_requestManager.MakeSystemRequest(SystemRequestType::RECREATE_ACTIVITY, nullptr, nullptr, "", "", 0);
}
// The design is a little weird, just a holdover from the old message. Can either toggle or set to on or off.
inline void System_ToggleFullscreenState(const std::string &param) {
g_requestManager.MakeSystemRequest(SystemRequestType::TOGGLE_FULLSCREEN_STATE, nullptr, nullptr, param, "", 0);

View file

@ -4,6 +4,7 @@
#include <vector>
#include <functional>
#include <cstdint>
#include <mutex>
// Platform integration
@ -57,12 +58,14 @@ void System_LaunchUrl(LaunchUrlType urlType, const char *url);
enum class SystemRequestType {
INPUT_TEXT_MODAL,
ASK_USERNAME_PASSWORD,
BROWSE_FOR_IMAGE,
BROWSE_FOR_FILE,
BROWSE_FOR_FOLDER,
EXIT_APP,
RESTART_APP, // For graphics backend changes
RECREATE_ACTIVITY, // Android
COPY_TO_CLIPBOARD,
SHARE_TEXT,
SET_WINDOW_TITLE,
@ -127,6 +130,8 @@ enum SystemProperty {
SYSPROP_HAS_BACK_BUTTON,
SYSPROP_HAS_KEYBOARD,
SYSPROP_HAS_OPEN_DIRECTORY,
SYSPROP_HAS_LOGIN_DIALOG,
SYSPROP_HAS_TEXT_INPUT_DIALOG, // Indicates that System_InputBoxGetString is available.
SYSPROP_CAN_CREATE_SHORTCUT,
@ -191,6 +196,7 @@ enum class SystemNotification {
SUSTAINED_PERF_CHANGE,
POLL_CONTROLLERS,
TOGGLE_DEBUG_CONSOLE, // TODO: Kinda weird, just ported forward.
TEST_JAVA_EXCEPTION,
};
std::string System_GetProperty(SystemProperty prop);
@ -206,18 +212,17 @@ std::vector<std::string> System_GetCameraDeviceList();
bool System_AudioRecordingIsAvailable();
bool System_AudioRecordingState();
// This will be changed to take an enum. Currently simply implemented by forwarding to NativeMessageReceived.
// This will be changed to take an enum. Replacement for the old NativeMessageReceived.
void System_PostUIMessage(const std::string &message, const std::string &param);
// Shows a visible message to the user.
// The default implementation in NativeApp.cpp uses our "osm" system (on screen messaging).
void System_NotifyUserMessage(const std::string &message, float duration = 1.0f, uint32_t color = 0x00FFFFFF, const char *id = nullptr);
// For these functions, most platforms will use the implementation provided in UI/AudioCommon.cpp,
// no need to implement separately.
void System_AudioGetDebugStats(char *buf, size_t bufSize);
void System_AudioClear();
// These samples really have 16 bits of value, but can be a little out of range.
// This is for pushing rate-controlled 44khz audio from emulation.
// If you push a little too fast, we'll pitch up to a limit, for example.
void System_AudioPushSamples(const int32_t *audio, int numSamples);
inline void System_AudioResetStatCounters() {

View file

@ -208,10 +208,12 @@ void UIContext::SetFontStyle(const UI::FontStyle &fontStyle) {
}
void UIContext::MeasureText(const UI::FontStyle &style, float scaleX, float scaleY, const char *str, float *x, float *y, int align) const {
_dbg_assert_(str != nullptr);
MeasureTextCount(style, scaleX, scaleY, str, (int)strlen(str), x, y, align);
}
void UIContext::MeasureTextCount(const UI::FontStyle &style, float scaleX, float scaleY, const char *str, int count, float *x, float *y, int align) const {
_dbg_assert_(str != nullptr);
if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) {
float sizeFactor = (float)style.sizePts / 24.0f;
Draw()->SetFontScale(scaleX * sizeFactor, scaleY * sizeFactor);
@ -225,6 +227,7 @@ void UIContext::MeasureTextCount(const UI::FontStyle &style, float scaleX, float
}
void UIContext::MeasureTextRect(const UI::FontStyle &style, float scaleX, float scaleY, const char *str, int count, const Bounds &bounds, float *x, float *y, int align) const {
_dbg_assert_(str != nullptr);
if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) {
float sizeFactor = (float)style.sizePts / 24.0f;
Draw()->SetFontScale(scaleX * sizeFactor, scaleY * sizeFactor);
@ -238,6 +241,7 @@ void UIContext::MeasureTextRect(const UI::FontStyle &style, float scaleX, float
}
void UIContext::DrawText(const char *str, float x, float y, uint32_t color, int align) {
_dbg_assert_(str != nullptr);
if (!textDrawer_ || (align & FLAG_DYNAMIC_ASCII)) {
// Use the font texture if this font is in that texture instead.
bool useFontTexture = Draw()->GetFontAtlas()->getFont(fontStyle_->atlasFont) != nullptr;
@ -286,6 +290,28 @@ void UIContext::DrawTextRect(const char *str, const Bounds &bounds, uint32_t col
RebindTexture();
}
static constexpr float MIN_TEXT_SCALE = 0.7f;
float UIContext::CalculateTextScale(const char *text, float availWidth, float availHeight) const {
float actualWidth, actualHeight;
Bounds availBounds(0, 0, availWidth, availHeight);
MeasureTextRect(theme->uiFont, 1.0f, 1.0f, text, (int)strlen(text), availBounds, &actualWidth, &actualHeight, ALIGN_VCENTER);
if (actualWidth > availWidth) {
return std::max(MIN_TEXT_SCALE, availWidth / actualWidth);
}
return 1.0f;
}
void UIContext::DrawTextRectSqueeze(const char *str, const Bounds &bounds, uint32_t color, int align) {
float origScaleX = fontScaleX_;
float origScaleY = fontScaleY_;
float scale = CalculateTextScale(str, bounds.w / origScaleX, bounds.h / origScaleY);
SetFontScale(scale * origScaleX, scale * origScaleY);
Bounds textBounds(bounds.x, bounds.y, bounds.w, bounds.h);
DrawTextRect(str, textBounds, color, align);
SetFontScale(origScaleX, origScaleY);
}
void UIContext::DrawTextShadowRect(const char *str, const Bounds &bounds, uint32_t color, int align) {
uint32_t alpha = (color >> 1) & 0xFF000000;
Bounds shadowBounds(bounds.x+2, bounds.y+2, bounds.w, bounds.h);
@ -313,6 +339,17 @@ void UIContext::FillRect(const UI::Drawable &drawable, const Bounds &bounds) {
}
}
void UIContext::DrawRectDropShadow(const Bounds &bounds, float radius, float alpha, uint32_t color) {
if (alpha <= 0.0f)
return;
color = colorAlpha(color, alpha);
// Bias the shadow downwards a bit.
Bounds shadowBounds = bounds.Expand(radius, 0.5 * radius, radius, 1.5 * radius);
Draw()->DrawImage4Grid(theme->dropShadow4Grid, shadowBounds.x, shadowBounds.y, shadowBounds.x2(), shadowBounds.y2(), color, radius * (1.0f / 24.0f) * 2.0f);
}
void UIContext::DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds) {
uidrawbuffer_->DrawImageStretchVGradient(image, bounds.x, bounds.y, bounds.x2(), bounds.y2(), color1, color2);
}

View file

@ -77,6 +77,8 @@ public:
void SetTintSaturation(float tint, float sat);
// High level drawing functions. They generally assume the default texture to be bounds.
void SetFontStyle(const UI::FontStyle &style);
const UI::FontStyle &GetFontStyle() { return *fontStyle_; }
void SetFontScale(float scaleX, float scaleY);
@ -87,7 +89,13 @@ public:
void DrawTextShadow(const char *str, float x, float y, uint32_t color, int align = 0);
void DrawTextRect(const char *str, const Bounds &bounds, uint32_t color, int align = 0);
void DrawTextShadowRect(const char *str, const Bounds &bounds, uint32_t color, int align = 0);
// Will squeeze the text into the bounds if needed.
void DrawTextRectSqueeze(const char *str, const Bounds &bounds, uint32_t color, int align = 0);
float CalculateTextScale(const char *text, float availWidth, float availHeight) const;
void FillRect(const UI::Drawable &drawable, const Bounds &bounds);
void DrawRectDropShadow(const Bounds &bounds, float radius, float alpha, uint32_t color = 0);
void DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds);
// in dps, like dp_xres and dp_yres

340
Common/UI/IconCache.cpp Normal file
View file

@ -0,0 +1,340 @@
#include <algorithm>
#include "Common/UI/IconCache.h"
#include "Common/UI/Context.h"
#include "Common/TimeUtil.h"
#include "Common/Data/Format/PNGLoad.h"
#include "Common/Log.h"
#define ICON_CACHE_VERSION 1
#define MK_FOURCC(str) (str[0] | ((uint8_t)str[1] << 8) | ((uint8_t)str[2] << 16) | ((uint8_t)str[3] << 24))
#define MAX_RUNTIME_CACHE_SIZE (1024 * 1024 * 4)
#define MAX_SAVED_CACHE_SIZE (1024 * 1024 * 1)
const uint32_t ICON_CACHE_MAGIC = MK_FOURCC("pICN");
IconCache g_iconCache;
struct DiskCacheHeader {
uint32_t magic;
uint32_t version;
uint32_t entryCount;
};
struct DiskCacheEntry {
uint32_t keyLen;
uint32_t dataLen;
IconFormat format;
double insertedTimestamp;
};
void IconCache::SaveToFile(FILE *file) {
std::unique_lock<std::mutex> lock(lock_);
// First, compute the total size. If above a threshold, remove until under.
Decimate(MAX_SAVED_CACHE_SIZE);
DiskCacheHeader header;
header.magic = ICON_CACHE_MAGIC;
header.version = ICON_CACHE_VERSION;
header.entryCount = (uint32_t)cache_.size();
fwrite(&header, 1, sizeof(header), file);
for (auto &iter : cache_) {
DiskCacheEntry entryHeader;
entryHeader.keyLen = (uint32_t)iter.first.size();
entryHeader.dataLen = (uint32_t)iter.second.data.size();
entryHeader.format = iter.second.format;
entryHeader.insertedTimestamp = iter.second.insertedTimeStamp;
fwrite(&entryHeader, 1, sizeof(entryHeader), file);
fwrite(iter.first.c_str(), 1, iter.first.size(), file);
fwrite(iter.second.data.data(), 1, iter.second.data.size(), file);
}
}
bool IconCache::LoadFromFile(FILE *file) {
std::unique_lock<std::mutex> lock(lock_);
DiskCacheHeader header;
if (fread(&header, 1, sizeof(header), file) != sizeof(DiskCacheHeader)) {
return false;
}
if (header.magic != ICON_CACHE_MAGIC || header.version != ICON_CACHE_VERSION) {
return false;
}
double now = time_now_d();
for (uint32_t i = 0; i < header.entryCount; i++) {
DiskCacheEntry entryHeader;
if (fread(&entryHeader, 1, sizeof(entryHeader), file) != sizeof(entryHeader)) {
break;
}
std::string key;
key.resize(entryHeader.keyLen, 0);
if (entryHeader.keyLen > 0x1000) {
// Let's say this is invalid, probably a corrupted file.
break;
}
fread(&key[0], 1, entryHeader.keyLen, file);
// Check if we already have the entry somehow.
if (cache_.find(key) != cache_.end()) {
// Seek past the data and go to the next entry.
fseek(file, entryHeader.dataLen, SEEK_CUR);
continue;
}
std::string data;
data.resize(entryHeader.dataLen);
size_t len = fread(&data[0], 1, entryHeader.dataLen, file);
if (len != (size_t)entryHeader.dataLen) {
// Stop reading and don't use this entry. Seems the file is truncated, but we'll recover.
break;
}
Entry entry{};
entry.data = data;
entry.format = entryHeader.format;
entry.insertedTimeStamp = entryHeader.insertedTimestamp;
entry.usedTimeStamp = now;
cache_.insert(std::pair<std::string, Entry>(key, entry));
}
return true;
}
void IconCache::ClearTextures() {
std::unique_lock<std::mutex> lock(lock_);
for (auto &iter : cache_) {
if (iter.second.texture) {
iter.second.texture->Release();
iter.second.texture = nullptr;
}
}
}
void IconCache::ClearData() {
ClearTextures();
std::unique_lock<std::mutex> lock(lock_);
cache_.clear();
}
void IconCache::FrameUpdate() {
std::unique_lock<std::mutex> lock(lock_);
// Remove old textures after a while.
double now = time_now_d();
if (now > lastUpdate_ + 2.0) {
for (auto &iter : cache_) {
double useAge = now - iter.second.usedTimeStamp;
if (useAge > 5.0) {
// Release the texture after a few seconds of no use.
// Still, keep the png data loaded, it's small.
if (iter.second.texture) {
iter.second.texture->Release();
iter.second.texture = nullptr;
}
}
}
lastUpdate_ = now;
}
if (now > lastDecimate_ + 60.0) {
Decimate(MAX_RUNTIME_CACHE_SIZE);
lastDecimate_ = now;
}
}
void IconCache::Decimate(int64_t maxSize) {
// Call this under the lock.
int64_t totalSize = 0;
for (auto &iter : cache_) {
totalSize += iter.second.data.size();
}
if (totalSize <= maxSize) {
return;
}
// Create a list of all the entries, sort by date. Then delete until we reach the desired size.
struct SortEntry {
std::string key;
double usedTimestamp;
size_t size;
};
std::vector<SortEntry> sortEntries;
for (auto iter : cache_) {
sortEntries.push_back({ iter.first, iter.second.usedTimeStamp, iter.second.data.size() });
}
std::sort(sortEntries.begin(), sortEntries.end(), [](const SortEntry &a, const SortEntry &b) {
// Oldest should be last in the lsit.
return a.usedTimestamp > b.usedTimestamp;
});
while (totalSize > maxSize && !sortEntries.empty()) {
totalSize -= sortEntries.back().size;
auto iter = cache_.find(sortEntries.back().key);
if (iter != cache_.end()) {
if (iter->second.texture) {
iter->second.texture->Release();
}
cache_.erase(iter);
}
sortEntries.pop_back();
}
}
bool IconCache::GetDimensions(const std::string &key, int *width, int *height) {
std::unique_lock<std::mutex> lock(lock_);
auto iter = cache_.find(key);
if (iter == cache_.end()) {
// Don't have this entry.
return false;
}
if (iter->second.texture) {
// TODO: Store the width/height in the cache.
*width = iter->second.texture->Width();
*height = iter->second.texture->Height();
return true;
} else {
return false;
}
}
bool IconCache::Contains(const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
return cache_.find(key) != cache_.end();
}
bool IconCache::MarkPending(const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
if (cache_.find(key) != cache_.end()) {
return false;
}
if (pending_.find(key) != pending_.end()) {
return false;
}
pending_.insert(key);
return true;
}
void IconCache::Cancel(const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
pending_.erase(key);
}
bool IconCache::InsertIcon(const std::string &key, IconFormat format, std::string &&data) {
std::unique_lock<std::mutex> lock(lock_);
if (key.empty()) {
return false;
}
if (data.empty()) {
ERROR_LOG(G3D, "Can't insert empty data into icon cache");
return false;
}
if (cache_.find(key) != cache_.end()) {
// Already have this entry.
return false;
}
if (data.size() > 1024 * 512) {
WARN_LOG(G3D, "Unusually large icon inserted in icon cache: %s (%d bytes)", key.c_str(), (int)data.size());
}
pending_.erase(key);
double now = time_now_d();
cache_.emplace(key, Entry{ std::move(data), format, nullptr, now, now, false });
return true;
}
Draw::Texture *IconCache::BindIconTexture(UIContext *context, const std::string &key) {
std::unique_lock<std::mutex> lock(lock_);
auto iter = cache_.find(key);
if (iter == cache_.end()) {
// Don't have this entry.
return nullptr;
}
if (iter->second.texture) {
context->GetDrawContext()->BindTexture(0, iter->second.texture);
iter->second.usedTimeStamp = time_now_d();
return iter->second.texture;
}
if (iter->second.badData) {
return nullptr;
}
// OK, don't have a texture. Upload it!
int width = 0;
int height = 0;
Draw::DataFormat dataFormat;
unsigned char *buffer = nullptr;
switch (iter->second.format) {
case IconFormat::PNG:
{
int result = pngLoadPtr((const unsigned char *)iter->second.data.data(), iter->second.data.size(), &width,
&height, &buffer);
if (result != 1) {
ERROR_LOG(G3D, "IconCache: Failed to load png (%d bytes) for key %s", (int)iter->second.data.size(), key.c_str());
iter->second.badData = true;
return nullptr;
}
dataFormat = Draw::DataFormat::R8G8B8A8_UNORM;
break;
}
default:
return nullptr;
}
Draw::TextureDesc iconDesc{};
iconDesc.width = width;
iconDesc.height = height;
iconDesc.depth = 1;
iconDesc.initData.push_back((const uint8_t *)buffer);
iconDesc.mipLevels = 1;
iconDesc.swizzle = Draw::TextureSwizzle::DEFAULT;
iconDesc.generateMips = false;
iconDesc.tag = key.c_str();
iconDesc.format = dataFormat;
iconDesc.type = Draw::TextureType::LINEAR2D;
Draw::Texture *texture = context->GetDrawContext()->CreateTexture(iconDesc);
iter->second.texture = texture;
iter->second.usedTimeStamp = time_now_d();
free(buffer);
return texture;
}
IconCacheStats IconCache::GetStats() {
IconCacheStats stats{};
std::unique_lock<std::mutex> lock(lock_);
for (auto &iter : cache_) {
stats.cachedCount++;
if (iter.second.texture)
stats.textureCount++;
stats.dataSize += iter.second.data.size();
}
stats.pending = pending_.size();
return stats;
}

75
Common/UI/IconCache.h Normal file
View file

@ -0,0 +1,75 @@
#pragma once
#include <map>
#include <set>
#include <string>
#include <cstdio>
#include <mutex>
#include <cstdint>
#include "Common/GPU/thin3d.h"
class UIContext;
enum class IconFormat : uint32_t {
PNG,
};
namespace Draw {
class Texture;
}
// TODO: Possibly make this smarter and use instead of ManagedTexture?
struct IconCacheStats {
size_t cachedCount;
size_t textureCount; // number of cached images that are "live" textures
size_t pending;
size_t dataSize;
};
class IconCache {
public:
// NOTE: Don't store the returned texture. Only use it to look up dimensions or other properties,
// instead call BindIconTexture every time you want to use it.
Draw::Texture *BindIconTexture(UIContext *context, const std::string &key);
// It's okay to call these from any thread.
bool MarkPending(const std::string &key); // returns false if already pending or loaded
void Cancel(const std::string &key);
bool InsertIcon(const std::string &key, IconFormat format, std::string &&pngData);
bool GetDimensions(const std::string &key, int *width, int *height);
bool Contains(const std::string &key);
void SaveToFile(FILE *file);
bool LoadFromFile(FILE *file);
void FrameUpdate();
void ClearTextures();
void ClearData();
IconCacheStats GetStats();
private:
void Decimate(int64_t maxSize);
struct Entry {
std::string data;
IconFormat format;
Draw::Texture *texture;
double insertedTimeStamp;
double usedTimeStamp;
bool badData;
};
std::map<std::string, Entry> cache_;
std::set<std::string> pending_;
std::mutex lock_;
double lastUpdate_ = 0.0;
double lastDecimate_ = 0.0;
};
extern IconCache g_iconCache;

View file

@ -1,5 +1,6 @@
#include <algorithm>
#include <sstream>
#include <cstring>
#include "Common/UI/PopupScreens.h"
#include "Common/UI/ViewGroup.h"
@ -7,6 +8,8 @@
#include "Common/UI/Root.h"
#include "Common/StringUtils.h"
#include "Common/Data/Text/I18n.h"
#include "Common/System/System.h"
#include "Common/System/Request.h"
namespace UI {
@ -495,6 +498,16 @@ PopupTextInputChoice::PopupTextInputChoice(std::string *value, const std::string
EventReturn PopupTextInputChoice::HandleClick(EventParams &e) {
restoreFocus_ = HasFocus();
// Choose method depending on platform capabilities.
if (System_GetPropertyBool(SYSPROP_HAS_TEXT_INPUT_DIALOG)) {
System_InputBoxGetString(text_, *value_ , [=](const std::string &enteredValue, int) {
*value_ = StripSpaces(enteredValue);
EventParams params{};
OnChange.Trigger(params);
});
return EVENT_DONE;
}
TextEditPopupScreen *popupScreen = new TextEditPopupScreen(value_, placeHolder_, ChopTitle(text_), maxLen_);
popupScreen->OnChange.Handle(this, &PopupTextInputChoice::HandleChange);
if (e.v)
@ -582,7 +595,12 @@ void AbstractChoiceWithValueDisplay::Draw(UIContext &dc) {
int paddingX = 12;
dc.SetFontStyle(dc.theme->uiFont);
const std::string valueText = ValueText();
std::string valueText = ValueText();
if (passwordDisplay_) {
// Replace all characters with stars.
memset(&valueText[0], '*', valueText.size());
}
// If there is a label, assume we want at least 20% of the size for it, at a minimum.
@ -636,4 +654,28 @@ std::string ChoiceWithValueDisplay::ValueText() const {
return valueText.str();
}
FileChooserChoice::FileChooserChoice(std::string *value, const std::string &text, BrowseFileType fileType, LayoutParams *layoutParams)
: AbstractChoiceWithValueDisplay(text, layoutParams), value_(value), fileType_(fileType) {
OnClick.Add([=](UI::EventParams &) {
System_BrowseForFile(text_, fileType, [=](const std::string &returnValue, int) {
if (*value_ != returnValue) {
*value = returnValue;
UI::EventParams e{};
e.s = *value;
OnChange.Trigger(e);
}
});
return UI::EVENT_DONE;
});
}
std::string FileChooserChoice::ValueText() const {
if (value_->empty()) {
auto di = GetI18NCategory(I18NCat::DIALOG);
return di->T("Default");
}
Path path(*value_);
return path.GetFilename();
}
} // namespace

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