diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8648726fd7..32ee69b692 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,7 +6,7 @@
# Core definitions
variables:
- GIT_SUBMODULE_STRATEGY: normal
+ GIT_SUBMODULE_STRATEGY: recursive
.core-defs:
variables:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bc81d9be2b..6cb38fb017 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,11 +1,11 @@
# vim:noexpandtab:
-cmake_minimum_required(VERSION 3.6)
+cmake_minimum_required(VERSION 3.8)
project(PPSSPP)
enable_testing()
#This is supposed to work but doesn't!
if(NOT ANDROID)
- set(CMAKE_CXX_STANDARD 11)
+ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()
@@ -120,8 +120,16 @@ if(OPENXR)
add_definitions(-DOPENXR)
add_library(openxr SHARED IMPORTED)
include_directories(ext/openxr)
- set_property(TARGET openxr PROPERTY IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/ext/openxr/libs/arm64-v8a/libopenxr_loader.so")
- message("OpenXR enabled")
+ if(OPENXR_PLATFORM_PICO)
+ add_definitions(-DOPENXR_PLATFORM_PICO)
+ set_property(TARGET openxr PROPERTY IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/ext/openxr/libs/pico/arm64-v8a/libopenxr_loader.so")
+ message("OpenXR for Pico enabled")
+ endif()
+ if(OPENXR_PLATFORM_QUEST)
+ add_definitions(-DOPENXR_PLATFORM_QUEST)
+ set_property(TARGET openxr PROPERTY IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/ext/openxr/libs/quest/arm64-v8a/libopenxr_loader.so")
+ message("OpenXR for Quest enabled")
+ endif()
endif()
if(GOLD)
@@ -315,7 +323,7 @@ if(NOT MSVC)
add_definitions(-DPNG_ARM_NEON_OPT=0)
if(ANDROID)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17")
endif()
if(CLANG)
add_definitions(-Wno-nullability-completeness)
@@ -370,8 +378,9 @@ if(NOT MSVC)
endif()
if(IOS)
+ set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0")
elseif(APPLE AND NOT CMAKE_CROSSCOMPILING)
- set(CMAKE_OSX_DEPLOYMENT_TARGET "10.8")
+ set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -U__STRICT_ANSI__")
set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++")
elseif(NOT ANDROID)
@@ -623,6 +632,8 @@ add_library(Common STATIC
Common/GPU/Vulkan/VulkanRenderManager.h
Common/GPU/Vulkan/VulkanQueueRunner.cpp
Common/GPU/Vulkan/VulkanQueueRunner.h
+ Common/GPU/Vulkan/VulkanFrameData.cpp
+ Common/GPU/Vulkan/VulkanFrameData.h
Common/Input/GestureDetector.cpp
Common/Input/GestureDetector.h
Common/Input/KeyCodes.h
@@ -1551,6 +1562,8 @@ set(GPU_SOURCES
GPU/Common/FragmentShaderGenerator.h
GPU/Common/VertexShaderGenerator.cpp
GPU/Common/VertexShaderGenerator.h
+ GPU/Common/GeometryShaderGenerator.cpp
+ GPU/Common/GeometryShaderGenerator.h
GPU/Common/FramebufferManagerCommon.cpp
GPU/Common/FramebufferManagerCommon.h
GPU/Common/GPUDebugInterface.cpp
@@ -2056,6 +2069,8 @@ endif()
target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash ${GlslangLibs}
${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS})
+target_compile_features(${CoreLibName} PUBLIC cxx_std_17)
+
if(FFmpeg_FOUND)
target_compile_definitions(${CoreLibName} PRIVATE USE_FFMPEG=1)
set_target_properties(${CoreLibName} PROPERTIES NO_SYSTEM_FROM_IMPORTED true)
@@ -2444,7 +2459,7 @@ if(NOT ANDROID)
endif()
# packaging and code signing
if(IOS)
- set(DEPLOYMENT_TARGET 8.0)
+ set(DEPLOYMENT_TARGET 11.0)
file(GLOB IOSAssets ios/assets/*.png)
list(REMOVE_ITEM IOSAssets ${CMAKE_CURRENT_SOURCE_DIR}/ios/assets/Default-568h@2x.png)
list(REMOVE_ITEM IOSAssets ${CMAKE_CURRENT_SOURCE_DIR}/ios/assets/Default-568h@3x.png)
@@ -2498,6 +2513,8 @@ if(UNIX AND NOT ANDROID AND NOT APPLE)
DIRECTORY "${CMAKE_BINARY_DIR}/assets"
DESTINATION "${CMAKE_INSTALL_DATADIR}/ppsspp"
PATTERN ".git*" EXCLUDE
+ PATTERN "mime" EXCLUDE
+ PATTERN "lang/README.md" EXCLUDE
)
install(
FILES "${CMAKE_BINARY_DIR}/ppsspp.desktop"
@@ -2510,7 +2527,11 @@ if(UNIX AND NOT ANDROID AND NOT APPLE)
)
install(
FILES "${CMAKE_SOURCE_DIR}/icons/icon-512.svg"
- DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pixmaps"
- RENAME ppsspp.svg
+ DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps"
+ RENAME "ppsspp.svg"
+ )
+ install(
+ FILES "${CMAKE_SOURCE_DIR}/assets/mime/ppsspp.xml"
+ DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/mime/packages"
)
endif()
diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj
index 2895cdc837..901006a0d9 100644
--- a/Common/Common.vcxproj
+++ b/Common/Common.vcxproj
@@ -153,6 +153,7 @@
ws2_32.lib
+ /ignore:4221 %(AdditionalOptions)
@@ -179,6 +180,7 @@
ws2_32.lib
+ /ignore:4221 %(AdditionalOptions)
@@ -205,6 +207,7 @@
ws2_32.lib
+ /ignore:4221 %(AdditionalOptions)
@@ -232,6 +235,7 @@
ws2_32.lib
+ /ignore:4221 %(AdditionalOptions)
@@ -264,6 +268,7 @@
ws2_32.lib
+ /ignore:4221 %(AdditionalOptions)
@@ -298,6 +303,7 @@
ws2_32.lib
+ /ignore:4221 %(AdditionalOptions)
@@ -332,6 +338,7 @@
ws2_32.lib
+ /ignore:4221 %(AdditionalOptions)
@@ -366,6 +373,7 @@
ws2_32.lib
+ /ignore:4221 %(AdditionalOptions)
@@ -441,6 +449,7 @@
+
@@ -861,6 +870,7 @@
+
diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters
index ff9fd9eaa6..991b03a71f 100644
--- a/Common/Common.vcxproj.filters
+++ b/Common/Common.vcxproj.filters
@@ -419,6 +419,9 @@
GPU\Vulkan
+
+ GPU\Vulkan
+
@@ -791,6 +794,9 @@
GPU\Vulkan
+
+ GPU\Vulkan
+
diff --git a/Common/Data/Collections/TinySet.h b/Common/Data/Collections/TinySet.h
index 9b4d91030d..4daf8279a3 100644
--- a/Common/Data/Collections/TinySet.h
+++ b/Common/Data/Collections/TinySet.h
@@ -10,21 +10,21 @@ struct TinySet {
~TinySet() { delete slowLookup_; }
inline void insert(const T &t) {
// Fast linear scan.
- for (int i = 0; i < fastCount; i++) {
+ for (int i = 0; i < fastCount_; i++) {
if (fastLookup_[i] == t)
return; // We already have it.
}
// Fast insertion
- if (fastCount < MaxFastSize) {
- fastLookup_[fastCount++] = t;
+ if (fastCount_ < MaxFastSize) {
+ fastLookup_[fastCount_++] = t;
return;
}
// Fall back to slow path.
insertSlow(t);
}
inline void push_back(const T &t) {
- if (fastCount < MaxFastSize) {
- fastLookup_[fastCount++] = t;
+ if (fastCount_ < MaxFastSize) {
+ fastLookup_[fastCount_++] = t;
return;
}
if (!slowLookup_) {
@@ -33,8 +33,8 @@ struct TinySet {
slowLookup_->push_back(t);
}
inline T *add_back() {
- if (fastCount < MaxFastSize) {
- return &fastLookup_[fastCount++];
+ if (fastCount_ < MaxFastSize) {
+ return &fastLookup_[fastCount_++];
}
if (!slowLookup_) {
slowLookup_ = new std::vector();
@@ -48,9 +48,9 @@ struct TinySet {
if (size() + otherSize <= MaxFastSize) {
// Fast case
for (size_t i = 0; i < otherSize; i++) {
- fastLookup_[fastCount + i] = other.fastLookup_[i];
+ fastLookup_[fastCount_ + i] = other.fastLookup_[i];
}
- fastCount += other.fastCount;
+ fastCount_ += other.fastCount_;
} else {
for (size_t i = 0; i < otherSize; i++) {
push_back(other[i]);
@@ -58,7 +58,7 @@ struct TinySet {
}
}
bool contains(T t) const {
- for (int i = 0; i < fastCount; i++) {
+ for (int i = 0; i < fastCount_; i++) {
if (fastLookup_[i] == t)
return true;
}
@@ -72,7 +72,7 @@ struct TinySet {
}
bool contains(const TinySet &otherSet) {
// Awkward, kind of ruins the fun.
- for (int i = 0; i < fastCount; i++) {
+ for (int i = 0; i < fastCount_; i++) {
if (otherSet.contains(fastLookup_[i]))
return true;
}
@@ -85,16 +85,17 @@ struct TinySet {
return false;
}
void clear() {
+ // TODO: Keep slowLookup_ around? That would be more similar to real vector behavior.
delete slowLookup_;
slowLookup_ = nullptr;
- fastCount = 0;
+ fastCount_ = 0;
}
bool empty() const {
- return fastCount == 0;
+ return fastCount_ == 0;
}
size_t size() const {
if (!slowLookup_) {
- return fastCount;
+ return fastCount_;
} else {
return slowLookup_->size() + MaxFastSize;
}
@@ -129,7 +130,7 @@ private:
}
slowLookup_->push_back(t);
}
+ int fastCount_ = 0; // first in the struct just so it's more visible in the VS debugger.
T fastLookup_[MaxFastSize];
- int fastCount = 0;
std::vector *slowLookup_ = nullptr;
};
diff --git a/Common/Data/Convert/SmallDataConvert.h b/Common/Data/Convert/SmallDataConvert.h
index a877295918..95558a17df 100644
--- a/Common/Data/Convert/SmallDataConvert.h
+++ b/Common/Data/Convert/SmallDataConvert.h
@@ -82,6 +82,20 @@ inline void Uint8x3ToFloat4(float f[4], uint32_t u) {
#endif
}
+inline void Uint8x3ToFloat3(float f[4], uint32_t u) {
+#if defined(_M_SSE) || PPSSPP_ARCH(ARM_NEON)
+ float temp[4];
+ Uint8x4ToFloat4(temp, u & 0xFFFFFF);
+ f[0] = temp[0];
+ f[1] = temp[1];
+ f[2] = temp[2];
+#else
+ f[0] = ((u >> 0) & 0xFF) * (1.0f / 255.0f);
+ f[1] = ((u >> 8) & 0xFF) * (1.0f / 255.0f);
+ f[2] = ((u >> 16) & 0xFF) * (1.0f / 255.0f);
+#endif
+}
+
inline void Uint8x3ToInt4(int i[4], uint32_t u) {
i[0] = ((u >> 0) & 0xFF);
i[1] = ((u >> 8) & 0xFF);
diff --git a/Common/Data/Encoding/Compression.cpp b/Common/Data/Encoding/Compression.cpp
index 751a9595cf..3e2e499a18 100644
--- a/Common/Data/Encoding/Compression.cpp
+++ b/Common/Data/Encoding/Compression.cpp
@@ -95,7 +95,6 @@ bool decompress_string(const std::string& str, std::string *dest) {
inflateEnd(&zs);
if (ret != Z_STREAM_END) { // an error occurred that was not EOF
- std::ostringstream oss;
ERROR_LOG(IO, "Exception during zlib decompression: (%i) %s", ret, zs.msg);
return false;
}
diff --git a/Common/Data/Format/IniFile.cpp b/Common/Data/Format/IniFile.cpp
index 32fcf43b68..cd2a658e5c 100644
--- a/Common/Data/Format/IniFile.cpp
+++ b/Common/Data/Format/IniFile.cpp
@@ -206,7 +206,7 @@ void Section::Set(const char* key, const char* newValue)
else
{
// The key did not already exist in this section - let's add it.
- lines.push_back(std::string(key) + " = " + EscapeComments(newValue));
+ lines.emplace_back(std::string(key) + " = " + EscapeComments(newValue));
}
}
@@ -272,7 +272,7 @@ void Section::Set(const char* key, const std::vector& newValues)
}
void Section::AddComment(const std::string &comment) {
- lines.push_back("# " + comment);
+ lines.emplace_back("# " + comment);
}
bool Section::Get(const char* key, std::vector& values)
diff --git a/Common/Data/Text/I18n.cpp b/Common/Data/Text/I18n.cpp
index 0ede4c516d..00771fe119 100644
--- a/Common/Data/Text/I18n.cpp
+++ b/Common/Data/Text/I18n.cpp
@@ -39,7 +39,7 @@ const char *I18NCategory::T(const char *key, const char *def) {
if (def)
missedKeyLog_[key] = def;
else
- missedKeyLog_[key] = modifiedKey.c_str();
+ missedKeyLog_[key] = modifiedKey;
// INFO_LOG(SYSTEM, "Missed translation key in %s: %s", name_.c_str(), key);
return def ? def : key;
}
diff --git a/Common/File/DirListing.cpp b/Common/File/DirListing.cpp
index abe5ff3af8..2f7178847b 100644
--- a/Common/File/DirListing.cpp
+++ b/Common/File/DirListing.cpp
@@ -148,7 +148,7 @@ std::vector ApplyFilter(std::vector files, const
std::string tmp;
while (*filter) {
if (*filter == ':') {
- filters.insert("." + tmp);
+ filters.emplace("." + tmp);
tmp.clear();
} else {
tmp.push_back(*filter);
@@ -156,7 +156,7 @@ std::vector ApplyFilter(std::vector files, const
filter++;
}
if (!tmp.empty())
- filters.insert("." + tmp);
+ filters.emplace("." + tmp);
}
auto pred = [&](const File::FileInfo &info) {
diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp
index ba3372e78a..39a31e164e 100644
--- a/Common/File/Path.cpp
+++ b/Common/File/Path.cpp
@@ -271,7 +271,7 @@ bool Path::CanNavigateUp() const {
if (type_ == PathType::CONTENT_URI) {
return AndroidContentURI(path_).CanNavigateUp();
}
- if (path_ == "/" || path_ == "") {
+ if (path_ == "/" || path_.empty()) {
return false;
}
if (type_ == PathType::HTTP) {
@@ -357,7 +357,6 @@ bool Path::ComputePathTo(const Path &other, std::string &path) const {
return true;
}
- std::string diff;
if (type_ == PathType::CONTENT_URI) {
AndroidContentURI a(path_);
AndroidContentURI b(other.path_);
diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp
index 2bce9ebaff..166a0ebe66 100644
--- a/Common/GPU/D3D11/thin3d_d3d11.cpp
+++ b/Common/GPU/D3D11/thin3d_d3d11.cpp
@@ -105,6 +105,7 @@ public:
void InvalidateCachedState() override;
void BindTextures(int start, int count, Texture **textures) override;
+ void BindNativeTexture(int index, void *nativeTexture) override;
void BindSamplerStates(int start, int count, SamplerState **states) override;
void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override;
void BindIndexBuffer(Buffer *indexBuffer, int offset) override;
@@ -269,6 +270,7 @@ D3D11DrawContext::D3D11DrawContext(ID3D11Device *device, ID3D11DeviceContext *de
caps_.anisoSupported = true;
caps_.textureNPOTFullySupported = true;
caps_.fragmentShaderDepthWriteSupported = true;
+ caps_.blendMinMaxSupported = true;
D3D11_FEATURE_DATA_D3D11_OPTIONS options{};
HRESULT result = device_->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, &options, sizeof(options));
@@ -471,6 +473,7 @@ static DXGI_FORMAT dataFormatToD3D11(DataFormat format) {
case DataFormat::R8G8B8A8_UNORM_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
case DataFormat::B8G8R8A8_UNORM: return DXGI_FORMAT_B8G8R8A8_UNORM;
case DataFormat::B8G8R8A8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
+ case DataFormat::R16_UNORM: return DXGI_FORMAT_R16_UNORM;
case DataFormat::R16_FLOAT: return DXGI_FORMAT_R16_FLOAT;
case DataFormat::R16G16_FLOAT: return DXGI_FORMAT_R16G16_FLOAT;
case DataFormat::R16G16B16A16_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT;
@@ -683,6 +686,7 @@ const char *semanticToD3D11(int semantic, UINT *index) {
switch (semantic) {
case SEM_POSITION: return "POSITION";
case SEM_COLOR0: *index = 0; return "COLOR";
+ case SEM_COLOR1: *index = 1; return "COLOR";
case SEM_TEXCOORD0: *index = 0; return "TEXCOORD";
case SEM_TEXCOORD1: *index = 1; return "TEXCOORD";
case SEM_NORMAL: return "NORMAL";
@@ -1388,6 +1392,12 @@ void D3D11DrawContext::BindTextures(int start, int count, Texture **textures) {
context_->PSSetShaderResources(start, count, views);
}
+void D3D11DrawContext::BindNativeTexture(int index, void *nativeTexture) {
+ // Collect the resource views from the textures.
+ ID3D11ShaderResourceView *view = (ID3D11ShaderResourceView *)nativeTexture;
+ context_->PSSetShaderResources(index, 1, &view);
+}
+
void D3D11DrawContext::BindSamplerStates(int start, int count, SamplerState **states) {
ID3D11SamplerState *samplers[MAX_BOUND_TEXTURES];
_assert_(start + count <= ARRAY_SIZE(samplers));
diff --git a/Common/GPU/D3D9/D3D9ShaderCompiler.cpp b/Common/GPU/D3D9/D3D9ShaderCompiler.cpp
index f41ea3911d..d3821066aa 100644
--- a/Common/GPU/D3D9/D3D9ShaderCompiler.cpp
+++ b/Common/GPU/D3D9/D3D9ShaderCompiler.cpp
@@ -47,7 +47,7 @@ LPD3DBLOB CompileShaderToByteCodeD3D9(const char *code, const char *target, std:
pShaderCode = nullptr;
}
} else {
- *errorMessage = "";
+ errorMessage->clear();
}
return pShaderCode;
diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp
index 8554961119..377a0b4050 100644
--- a/Common/GPU/D3D9/thin3d_d3d9.cpp
+++ b/Common/GPU/D3D9/thin3d_d3d9.cpp
@@ -23,6 +23,7 @@
#include "Common/Math/lin/matrix4x4.h"
#include "Common/GPU/thin3d.h"
#include "Common/GPU/D3D9/D3D9StateCache.h"
+#include "Common/OSVersion.h"
#include "Common/StringUtils.h"
#include "Common/Log.h"
@@ -114,6 +115,7 @@ static const D3DSTENCILOP stencilOpToD3D9[] = {
D3DFORMAT FormatToD3DFMT(DataFormat fmt) {
switch (fmt) {
+ case DataFormat::R16_UNORM: return D3DFMT_L16; // closest match, should be a fine substitution if we ignore channels except R.
case DataFormat::R8G8B8A8_UNORM: return D3DFMT_A8R8G8B8;
case DataFormat::B8G8R8A8_UNORM: return D3DFMT_A8R8G8B8;
case DataFormat::R4G4B4A4_UNORM_PACK16: return D3DFMT_A4R4G4B4; // emulated
@@ -442,6 +444,17 @@ void D3D9Texture::SetImageData(int x, int y, int z, int width, int height, int d
if (data != rect.pBits)
memcpy(dest, source, sizeof(uint32_t) * width);
break;
+
+ case DataFormat::R8_UNORM:
+ if (data != rect.pBits)
+ memcpy(dest, source, width);
+ break;
+
+ case DataFormat::R16_UNORM:
+ if (data != rect.pBits)
+ memcpy(dest, source, sizeof(uint16_t) * width);
+ break;
+
default:
// Unhandled data format copy.
DebugBreak();
@@ -520,6 +533,8 @@ public:
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
void BindTextures(int start, int count, Texture **textures) override;
+ void BindNativeTexture(int index, void *nativeTexture) override;
+
void BindSamplerStates(int start, int count, SamplerState **states) override {
_assert_(start + count <= MAX_BOUND_TEXTURES);
for (int i = 0; i < count; ++i) {
@@ -632,6 +647,63 @@ void D3D9Context::InvalidateCachedState() {
curPipeline_ = nullptr;
}
+// TODO: Move this detection elsewhere when it's needed elsewhere, not before. It's ugly.
+// Source: https://envytools.readthedocs.io/en/latest/hw/pciid.html#gf100
+enum NVIDIAGeneration {
+ NV_PRE_KEPLER,
+ NV_KEPLER,
+ NV_MAXWELL,
+ NV_PASCAL,
+ NV_VOLTA,
+ NV_TURING, // or later
+};
+
+static NVIDIAGeneration NVIDIAGetDeviceGeneration(int deviceID) {
+ if (deviceID >= 0x1180 && deviceID <= 0x11bf)
+ return NV_KEPLER; // GK104
+ if (deviceID >= 0x11c0 && deviceID <= 0x11fa)
+ return NV_KEPLER; // GK106
+ if (deviceID >= 0x0fc0 && deviceID <= 0x0fff)
+ return NV_KEPLER; // GK107
+ if (deviceID >= 0x1003 && deviceID <= 0x1028)
+ return NV_KEPLER; // GK110(B)
+ if (deviceID >= 0x1280 && deviceID <= 0x12ba)
+ return NV_KEPLER; // GK208
+ if (deviceID >= 0x1381 && deviceID <= 0x13b0)
+ return NV_MAXWELL; // GM107
+ if (deviceID >= 0x1340 && deviceID <= 0x134d)
+ return NV_MAXWELL; // GM108
+ if (deviceID >= 0x13c0 && deviceID <= 0x13d9)
+ return NV_MAXWELL; // GM204
+ if (deviceID >= 0x1401 && deviceID <= 0x1427)
+ return NV_MAXWELL; // GM206
+ if (deviceID >= 0x15f7 && deviceID <= 0x15f9)
+ return NV_PASCAL; // GP100
+ if (deviceID >= 0x15f7 && deviceID <= 0x15f9)
+ return NV_PASCAL; // GP100
+ if (deviceID >= 0x1b00 && deviceID <= 0x1b38)
+ return NV_PASCAL; // GP102
+ if (deviceID >= 0x1b80 && deviceID <= 0x1be1)
+ return NV_PASCAL; // GP104
+ if (deviceID >= 0x1c02 && deviceID <= 0x1c62)
+ return NV_PASCAL; // GP106
+ if (deviceID >= 0x1c81 && deviceID <= 0x1c92)
+ return NV_PASCAL; // GP107
+ if (deviceID >= 0x1d01 && deviceID <= 0x1d12)
+ return NV_PASCAL; // GP108
+ if (deviceID >= 0x1d81 && deviceID <= 0x1dba)
+ return NV_VOLTA; // GV100
+ if (deviceID >= 0x1e02 && deviceID <= 0x1e3c)
+ return NV_TURING; // TU102
+ if (deviceID >= 0x1e82 && deviceID <= 0x1ed0)
+ return NV_TURING; // TU104
+ if (deviceID >= 0x1f02 && deviceID <= 0x1f51)
+ return NV_TURING; // TU104
+ if (deviceID >= 0x1e02)
+ return NV_TURING; // More TU models or later, probably.
+ return NV_PRE_KEPLER;
+}
+
#define FB_DIV 1
#define FOURCC_INTZ ((D3DFORMAT)(MAKEFOURCC('I', 'N', 'T', 'Z')))
@@ -651,14 +723,24 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID
caps_.vendor = GPUVendor::VENDOR_UNKNOWN;
}
- if (!FAILED(device->GetDeviceCaps(&d3dCaps_))) {
+ D3DCAPS9 caps;
+ ZeroMemory(&caps, sizeof(caps));
+ HRESULT result = 0;
+ if (deviceEx_) {
+ result = deviceEx_->GetDeviceCaps(&caps);
+ } else {
+ result = device_->GetDeviceCaps(&caps);
+ }
+
+ if (SUCCEEDED(result)) {
sprintf(shadeLangVersion_, "PS: %04x VS: %04x", d3dCaps_.PixelShaderVersion & 0xFFFF, d3dCaps_.VertexShaderVersion & 0xFFFF);
} else {
+ WARN_LOG(G3D, "Direct3D9: Failed to get the device caps!");
strcpy(shadeLangVersion_, "N/A");
}
+
caps_.deviceID = identifier_.DeviceId;
caps_.multiViewport = false;
- caps_.anisoSupported = true;
caps_.depthRangeMinusOneToOne = false;
caps_.preferredDepthBufferFormat = DataFormat::D24_S8;
caps_.dualSourceBlend = false;
@@ -670,8 +752,30 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID
caps_.framebufferDepthCopySupported = false;
caps_.framebufferSeparateDepthCopySupported = false;
caps_.texture3DSupported = true;
- caps_.textureNPOTFullySupported = true;
caps_.fragmentShaderDepthWriteSupported = true;
+ caps_.blendMinMaxSupported = true;
+
+ if ((caps.RasterCaps & D3DPRASTERCAPS_ANISOTROPY) != 0 && caps.MaxAnisotropy > 1) {
+ caps_.anisoSupported = true;
+ }
+ if ((caps.TextureCaps & (D3DPTEXTURECAPS_NONPOW2CONDITIONAL | D3DPTEXTURECAPS_POW2)) == 0) {
+ caps_.textureNPOTFullySupported = true;
+ }
+
+ // VS range culling (killing triangles in the vertex shader using NaN) causes problems on Intel.
+ // Also causes problems on old NVIDIA.
+ switch (caps_.vendor) {
+ case Draw::GPUVendor::VENDOR_INTEL:
+ bugs_.Infest(Bugs::BROKEN_NAN_IN_CONDITIONAL);
+ break;
+ case Draw::GPUVendor::VENDOR_NVIDIA:
+ // Older NVIDIAs don't seem to like NaNs in their DX9 vertex shaders.
+ // No idea if KEPLER is the right cutoff, but let's go with it.
+ if (NVIDIAGetDeviceGeneration(caps_.deviceID) < NV_KEPLER) {
+ bugs_.Infest(Bugs::BROKEN_NAN_IN_CONDITIONAL);
+ }
+ break;
+ }
if (d3d) {
D3DDISPLAYMODE displayMode;
@@ -679,8 +783,9 @@ D3D9Context::D3D9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapterId, ID
// To be safe, make sure both the display format and the FBO format support INTZ.
HRESULT displayINTZ = d3d->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, displayMode.Format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, FOURCC_INTZ);
- HRESULT fboINTZ = d3d->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, D3DFMT_A8R8G8B8, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, FOURCC_INTZ);
- supportsINTZ = SUCCEEDED(displayINTZ) && SUCCEEDED(fboINTZ);
+ HRESULT displayINTY = d3d->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, displayMode.Format, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, ((D3DFORMAT)(MAKEFOURCC('I', 'N', 'T', 'Y'))));
+ // Try to prevent INTZ on older Intel drivers that claim support.
+ supportsINTZ = SUCCEEDED(displayINTZ) && !SUCCEEDED(displayINTY) && IsWin7OrHigher();
}
caps_.textureDepthSupported = supportsINTZ;
@@ -813,6 +918,11 @@ void D3D9Context::BindTextures(int start, int count, Texture **textures) {
}
}
+void D3D9Context::BindNativeTexture(int index, void *nativeTexture) {
+ LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)nativeTexture;
+ device_->SetTexture(index, texture);
+}
+
void D3D9Context::EndFrame() {
curPipeline_ = nullptr;
}
@@ -835,6 +945,10 @@ static void SemanticToD3D9UsageAndIndex(int semantic, BYTE *usage, BYTE *index)
case SEM_COLOR0:
*usage = D3DDECLUSAGE_COLOR;
break;
+ case SEM_COLOR1:
+ *usage = D3DDECLUSAGE_COLOR;
+ *index = 1;
+ break;
case SEM_TEXCOORD0:
*usage = D3DDECLUSAGE_TEXCOORD;
break;
diff --git a/Common/GPU/DataFormat.h b/Common/GPU/DataFormat.h
index b07d0a2f34..0c8a93e627 100644
--- a/Common/GPU/DataFormat.h
+++ b/Common/GPU/DataFormat.h
@@ -32,6 +32,8 @@ enum class DataFormat : uint8_t {
A1R5G5B5_UNORM_PACK16, // A1 in the UPPER bit.
A1B5G5R5_UNORM_PACK16, // A1 in the UPPER bit. OpenGL-only.
+ R16_UNORM,
+
R16_FLOAT,
R16G16_FLOAT,
R16G16B16A16_FLOAT,
diff --git a/Common/GPU/OpenGL/DataFormatGL.cpp b/Common/GPU/OpenGL/DataFormatGL.cpp
index 1b8ba63acc..48e439a76d 100644
--- a/Common/GPU/OpenGL/DataFormatGL.cpp
+++ b/Common/GPU/OpenGL/DataFormatGL.cpp
@@ -4,9 +4,23 @@
namespace Draw {
// TODO: Also output storage format (GL_RGBA8 etc) for modern GL usage.
-bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) {
+bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) {
alignment = 4;
switch (fmt) {
+ case DataFormat::R16_UNORM:
+ internalFormat = GL_RGBA;
+ format = GL_RED;
+ type = GL_UNSIGNED_SHORT;
+ alignment = 2;
+ break;
+
+ case DataFormat::R8_UNORM:
+ internalFormat = GL_RGBA;
+ format = GL_RED;
+ type = GL_UNSIGNED_BYTE;
+ alignment = 1;
+ break;
+
case DataFormat::R8G8B8A8_UNORM:
internalFormat = GL_RGBA;
format = GL_RGBA;
diff --git a/Common/GPU/OpenGL/DataFormatGL.h b/Common/GPU/OpenGL/DataFormatGL.h
index 63fc0a22ec..163f155db3 100644
--- a/Common/GPU/OpenGL/DataFormatGL.h
+++ b/Common/GPU/OpenGL/DataFormatGL.h
@@ -5,6 +5,6 @@
namespace Draw {
-bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment);
+bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment);
}
diff --git a/Common/GPU/OpenGL/GLFeatures.cpp b/Common/GPU/OpenGL/GLFeatures.cpp
index bf9a639e67..aa79ac5941 100644
--- a/Common/GPU/OpenGL/GLFeatures.cpp
+++ b/Common/GPU/OpenGL/GLFeatures.cpp
@@ -51,7 +51,7 @@ static void ParseExtensionsString(const std::string& str, std::set
size_t next = 0;
for (size_t pos = 0, len = str.length(); pos < len; ++pos) {
if (str[pos] == ' ') {
- output.insert(str.substr(next, pos - next));
+ output.emplace(str.substr(next, pos - next));
// Skip the delimiter itself.
next = pos + 1;
}
@@ -60,7 +60,7 @@ static void ParseExtensionsString(const std::string& str, std::set
if (next == 0 && str.length() != 0) {
output.insert(str);
} else if (next < str.length()) {
- output.insert(str.substr(next));
+ output.emplace(str.substr(next));
}
}
diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp
index e19075f109..80222bbfbf 100644
--- a/Common/GPU/OpenGL/GLQueueRunner.cpp
+++ b/Common/GPU/OpenGL/GLQueueRunner.cpp
@@ -252,8 +252,8 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps, bool ski
ERROR_LOG(G3D, "Could not link program:\n %s", infoLog.c_str());
ERROR_LOG(G3D, "VS desc:\n%s", vsDesc.c_str());
ERROR_LOG(G3D, "FS desc:\n%s", fsDesc.c_str());
- ERROR_LOG(G3D, "VS:\n%s\n", vsCode);
- ERROR_LOG(G3D, "FS:\n%s\n", fsCode);
+ ERROR_LOG(G3D, "VS:\n%s\n", LineNumberString(vsCode).c_str());
+ ERROR_LOG(G3D, "FS:\n%s\n", LineNumberString(fsCode).c_str());
#ifdef _WIN32
OutputDebugStringUTF8(infoLog.c_str());
@@ -385,7 +385,7 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps, bool ski
GLenum internalFormat, format, type;
int alignment;
- Thin3DFormatToFormatAndType(step.texture_image.format, internalFormat, format, type, alignment);
+ Thin3DFormatToGLFormatAndType(step.texture_image.format, internalFormat, format, type, alignment);
if (step.texture_image.depth == 1) {
glTexImage2D(tex->target,
step.texture_image.level, internalFormat,
@@ -701,7 +701,13 @@ void GLQueueRunner::RunSteps(const std::vector &steps, bool skipGLCal
switch (step.stepType) {
case GLRStepType::RENDER:
renderCount++;
- PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount);
+ if (IsVRBuild()) {
+ GLRStep vrStep = step;
+ PreprocessStepVR(&vrStep);
+ PerformRenderPass(vrStep, renderCount == 1, renderCount == totalRenderCount);
+ } else {
+ PerformRenderPass(step, renderCount == 1, renderCount == totalRenderCount);
+ }
break;
case GLRStepType::COPY:
PerformCopy(step);
@@ -811,7 +817,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
int logicOp = -1;
bool logicEnabled = false;
#endif
- bool clipDistance0Enabled = false;
+ bool clipDistanceEnabled[8]{};
GLuint blendEqColor = (GLuint)-1;
GLuint blendEqAlpha = (GLuint)-1;
@@ -1119,14 +1125,18 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
{
if (curProgram != c.program.program) {
glUseProgram(c.program.program->program);
- if (c.program.program->use_clip_distance0 != clipDistance0Enabled) {
- if (c.program.program->use_clip_distance0)
- glEnable(GL_CLIP_DISTANCE0);
- else
- glDisable(GL_CLIP_DISTANCE0);
- clipDistance0Enabled = c.program.program->use_clip_distance0;
- }
curProgram = c.program.program;
+
+ for (size_t i = 0; i < ARRAY_SIZE(clipDistanceEnabled); ++i) {
+ if (c.program.program->use_clip_distance[i] == clipDistanceEnabled[i])
+ continue;
+
+ if (c.program.program->use_clip_distance[i])
+ glEnable(GL_CLIP_DISTANCE0 + (GLenum)i);
+ else
+ glDisable(GL_CLIP_DISTANCE0 + (GLenum)i);
+ clipDistanceEnabled[i] = c.program.program->use_clip_distance[i];
+ }
}
CHECK_GL_ERROR_IF_DEBUG();
break;
@@ -1279,7 +1289,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
// For things to show in RenderDoc, need to split into glTexImage2D(..., nullptr) and glTexSubImage.
GLuint internalFormat, format, type;
int alignment;
- Thin3DFormatToFormatAndType(c.texture_subimage.format, internalFormat, format, type, alignment);
+ Thin3DFormatToGLFormatAndType(c.texture_subimage.format, internalFormat, format, type, alignment);
glTexSubImage2D(tex->target, c.texture_subimage.level, c.texture_subimage.x, c.texture_subimage.y, c.texture_subimage.width, c.texture_subimage.height, format, type, c.texture_subimage.data);
if (c.texture_subimage.allocType == GLRAllocType::ALIGNED) {
FreeAlignedMemory(c.texture_subimage.data);
@@ -1366,8 +1376,10 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last
glDisable(GL_COLOR_LOGIC_OP);
}
#endif
- if (clipDistance0Enabled)
- glDisable(GL_CLIP_DISTANCE0);
+ for (size_t i = 0; i < ARRAY_SIZE(clipDistanceEnabled); ++i) {
+ if (clipDistanceEnabled[i])
+ glDisable(GL_CLIP_DISTANCE0 + (GLenum)i);
+ }
if ((colorMask & 15) != 15)
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
CHECK_GL_ERROR_IF_DEBUG();
diff --git a/Common/GPU/OpenGL/GLRenderManager.cpp b/Common/GPU/OpenGL/GLRenderManager.cpp
index ae5cb9dd17..01cccb8874 100644
--- a/Common/GPU/OpenGL/GLRenderManager.cpp
+++ b/Common/GPU/OpenGL/GLRenderManager.cpp
@@ -236,28 +236,12 @@ bool GLRenderManager::ThreadFrame() {
firstFrame = false;
}
- // Start of an OpenXR frame. This updates user's head pose and VR timestamps.
- // For fluent rendering, delay between StartVRRender and FinishVRRender must be very short.
- if (IsVRBuild() && !vrRenderStarted) {
- if (StartVRRender()) {
- vrRenderStarted = true;
- } else {
- return false;
- }
- }
-
// Render the scene.
Run(threadFrame_);
VLOG("PULL: Finished frame %d", threadFrame_);
} while (!nextFrame);
- // Post OpenXR frame on a screen.
- if (IsVRBuild() && vrRenderStarted) {
- FinishVRRender();
- vrRenderStarted = false;
- }
-
return true;
}
@@ -579,7 +563,6 @@ void GLRenderManager::EndSubmitFrame(int frame) {
void GLRenderManager::Run(int frame) {
BeginSubmitFrame(frame);
-
FrameData &frameData = frameData_[frame];
auto &stepsOnThread = frameData_[frame].steps;
diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h
index 719603863e..51d7e1e9eb 100644
--- a/Common/GPU/OpenGL/GLRenderManager.h
+++ b/Common/GPU/OpenGL/GLRenderManager.h
@@ -91,6 +91,13 @@ public:
std::string error;
};
+struct GLRProgramFlags {
+ bool supportDualSource : 1;
+ bool useClipDistance0 : 1;
+ bool useClipDistance1 : 1;
+ bool useClipDistance2 : 1;
+};
+
class GLRProgram {
public:
~GLRProgram() {
@@ -119,7 +126,7 @@ public:
std::vector semantics_;
std::vector queries_;
std::vector initialize_;
- bool use_clip_distance0 = false;
+ bool use_clip_distance[8]{};
struct UniformInfo {
int loc_;
@@ -427,15 +434,17 @@ public:
// not be an active render pass.
GLRProgram *CreateProgram(
std::vector shaders, std::vector semantics, std::vector queries,
- std::vector initializers, bool supportDualSource, bool useClipDistance0) {
+ std::vector initializers, const GLRProgramFlags &flags) {
GLRInitStep step{ GLRInitStepType::CREATE_PROGRAM };
_assert_(shaders.size() <= ARRAY_SIZE(step.create_program.shaders));
step.create_program.program = new GLRProgram();
step.create_program.program->semantics_ = semantics;
step.create_program.program->queries_ = queries;
step.create_program.program->initialize_ = initializers;
- step.create_program.program->use_clip_distance0 = useClipDistance0;
- step.create_program.support_dual_source = supportDualSource;
+ step.create_program.program->use_clip_distance[0] = flags.useClipDistance0;
+ step.create_program.program->use_clip_distance[1] = flags.useClipDistance1;
+ step.create_program.program->use_clip_distance[2] = flags.useClipDistance2;
+ step.create_program.support_dual_source = flags.supportDualSource;
_assert_msg_(shaders.size() > 0, "Can't create a program with zero shaders");
for (size_t i = 0; i < shaders.size(); i++) {
step.create_program.shaders[i] = shaders[i];
@@ -1003,6 +1012,7 @@ private:
bool readyForFence = true;
bool readyForRun = false;
bool readyForSubmit = false;
+
bool skipSwap = false;
GLRRunType type = GLRRunType::END;
@@ -1041,7 +1051,6 @@ private:
bool nextFrame = false;
bool firstFrame = true;
- bool vrRenderStarted = false;
GLDeleter deleter_;
bool skipGLCalls_ = false;
diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp
index 2e93fc8791..95e737e63a 100644
--- a/Common/GPU/OpenGL/thin3d_gl.cpp
+++ b/Common/GPU/OpenGL/thin3d_gl.cpp
@@ -401,6 +401,8 @@ public:
}
void BindTextures(int start, int count, Texture **textures) override;
+ void BindNativeTexture(int sampler, void *nativeTexture) override;
+
void BindPipeline(Pipeline *pipeline) override;
void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override {
_assert_(start + count <= ARRAY_SIZE(curVBuffers_));
@@ -550,6 +552,8 @@ OpenGLContext::OpenGLContext() {
caps_.framebufferDepthBlitSupported = caps_.framebufferBlitSupported;
caps_.framebufferStencilBlitSupported = caps_.framebufferBlitSupported;
caps_.depthClampSupported = gl_extensions.ARB_depth_clamp;
+ caps_.blendMinMaxSupported = gl_extensions.EXT_blend_minmax;
+
if (gl_extensions.IsGLES) {
caps_.clipDistanceSupported = gl_extensions.EXT_clip_cull_distance || gl_extensions.APPLE_clip_distance;
caps_.cullDistanceSupported = gl_extensions.EXT_clip_cull_distance;
@@ -709,8 +713,10 @@ OpenGLContext::OpenGLContext() {
}
}
- if (gl_extensions.IsGLES) {
+ // NOTE: We only support framebuffer fetch on ES3 due to past issues..
+ if (gl_extensions.IsGLES && gl_extensions.GLES3) {
caps_.framebufferFetchSupported = (gl_extensions.EXT_shader_framebuffer_fetch || gl_extensions.ARM_shader_framebuffer_fetch);
+
if (gl_extensions.EXT_shader_framebuffer_fetch) {
shaderLanguageDesc_.framebufferFetchExtension = "#extension GL_EXT_shader_framebuffer_fetch : require";
shaderLanguageDesc_.lastFragData = gl_extensions.GLES3 ? "fragColor0" : "gl_LastFragData[0]";
@@ -1138,6 +1144,12 @@ void OpenGLContext::BindTextures(int start, int count, Texture **textures) {
}
}
+void OpenGLContext::BindNativeTexture(int index, void *nativeTexture) {
+ GLRTexture *tex = (GLRTexture *)nativeTexture;
+ boundTextures_[index] = tex;
+ renderManager_.BindTexture(index, tex);
+}
+
void OpenGLContext::ApplySamplers() {
for (int i = 0; i < MAX_TEXTURE_SLOTS; i++) {
const OpenGLSamplerState *samp = boundSamplers_[i];
@@ -1191,9 +1203,11 @@ bool OpenGLPipeline::LinkShaders() {
}
std::vector semantics;
+ semantics.reserve(8);
// Bind all the common vertex data points. Mismatching ones will be ignored.
semantics.push_back({ SEM_POSITION, "Position" });
semantics.push_back({ SEM_COLOR0, "Color0" });
+ semantics.push_back({ SEM_COLOR1, "Color1" });
semantics.push_back({ SEM_TEXCOORD0, "TexCoord0" });
semantics.push_back({ SEM_NORMAL, "Normal" });
semantics.push_back({ SEM_TANGENT, "Tangent" });
@@ -1226,7 +1240,8 @@ bool OpenGLPipeline::LinkShaders() {
}
}
- program_ = render_->CreateProgram(linkShaders, semantics, queries, initialize, false, false);
+ GLRProgramFlags flags{};
+ program_ = render_->CreateProgram(linkShaders, semantics, queries, initialize, flags);
return true;
}
@@ -1483,7 +1498,8 @@ uint32_t OpenGLContext::GetDataFormatSupport(DataFormat fmt) const {
return FMT_INPUTLAYOUT;
case DataFormat::R8_UNORM:
- return 0;
+ return FMT_TEXTURE;
+
case DataFormat::BC1_RGBA_UNORM_BLOCK:
case DataFormat::BC2_UNORM_BLOCK:
case DataFormat::BC3_UNORM_BLOCK:
diff --git a/Common/GPU/ShaderTranslation.cpp b/Common/GPU/ShaderTranslation.cpp
index 34de213451..9391d1b7da 100644
--- a/Common/GPU/ShaderTranslation.cpp
+++ b/Common/GPU/ShaderTranslation.cpp
@@ -152,16 +152,18 @@ std::string Postprocess(std::string code, ShaderLanguage lang, ShaderStage stage
return output;
}
+static_assert(Draw::SEM_TEXCOORD0 == 3, "Semantic shader hardcoded in glsl below.");
+
bool ConvertToVulkanGLSL(std::string *dest, TranslatedShaderMetadata *destMetadata, std::string src, ShaderStage stage, std::string *errorMessage) {
std::stringstream out;
- static struct {
+ static const struct {
ShaderStage stage;
const char *needle;
const char *replacement;
} replacements[] = {
{ ShaderStage::Vertex, "attribute vec4 a_position;", "layout(location = 0) in vec4 a_position;" },
- { ShaderStage::Vertex, "attribute vec2 a_texcoord0;", "layout(location = 2) in vec2 a_texcoord0;"},
+ { ShaderStage::Vertex, "attribute vec2 a_texcoord0;", "layout(location = 3) in vec2 a_texcoord0;"},
{ ShaderStage::Vertex, "varying vec2 v_position;", "layout(location = 0) out vec2 v_position;" },
{ ShaderStage::Fragment, "varying vec2 v_position;", "layout(location = 0) in vec2 v_position;" },
{ ShaderStage::Fragment, "texture2D(", "texture(" },
@@ -233,7 +235,7 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, const ShaderLan
return false;
#endif
- *errorMessage = "";
+ errorMessage->clear();
glslang::TProgram program;
const char *shaderStrings[1]{};
diff --git a/Common/GPU/ShaderWriter.cpp b/Common/GPU/ShaderWriter.cpp
index b260d05fc9..7ca6deaeb0 100644
--- a/Common/GPU/ShaderWriter.cpp
+++ b/Common/GPU/ShaderWriter.cpp
@@ -53,6 +53,30 @@ static const char * const vulkan_glsl_preamble_vs =
"precision highp float;\n"
"\n";
+static const char * const hlsl_preamble_gs =
+"#define vec2 float2\n"
+"#define vec3 float3\n"
+"#define vec4 float4\n"
+"#define ivec2 int2\n"
+"#define ivec4 int4\n"
+"#define mat2 float2x2\n"
+"#define mat4 float4x4\n"
+"#define mat3x4 float4x3\n" // note how the conventions are backwards
+"#define splat3(x) vec3(x, x, x)\n"
+"#define lowp\n"
+"#define mediump\n"
+"#define highp\n"
+"\n";
+
+static const char * const vulkan_glsl_preamble_gs =
+"#version 450\n"
+"#extension GL_ARB_separate_shader_objects : enable\n"
+"#extension GL_ARB_shading_language_420pack : enable\n"
+"#define mul(x, y) ((x) * (y))\n"
+"#define splat3(x) vec3(x)\n"
+"precision highp float;\n"
+"\n";
+
static const char * const hlsl_preamble_vs =
"#define vec2 float2\n"
"#define vec3 float3\n"
@@ -68,15 +92,17 @@ static const char * const hlsl_preamble_vs =
"#define highp\n"
"\n";
-static const char * const semanticNames[8] = {
+static const char * const semanticNames[] = {
"POSITION",
"COLOR0",
+ "COLOR1",
"TEXCOORD0",
"TEXCOORD1",
"NORMAL",
"TANGENT",
"BINORMAL",
};
+static_assert(ARRAY_SIZE(semanticNames) == Draw::SEM_MAX, "Missing semantic in semanticNames");
// Unsafe. But doesn't matter, we'll use big buffers for shader gen.
ShaderWriter & ShaderWriter::F(const char *format, ...) {
@@ -97,6 +123,9 @@ void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions
case ShaderStage::Fragment:
W(vulkan_glsl_preamble_fs);
break;
+ case ShaderStage::Geometry:
+ W(vulkan_glsl_preamble_gs);
+ break;
default:
break;
}
@@ -115,6 +144,9 @@ void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions
W(hlsl_d3d11_preamble_fs);
}
break;
+ case ShaderStage::Geometry:
+ W(hlsl_preamble_gs);
+ break;
default:
break;
}
@@ -145,6 +177,11 @@ void ShaderWriter::Preamble(const char **gl_extensions, size_t num_gl_extensions
}
C("#define gl_VertexIndex gl_VertexID\n");
break;
+ case ShaderStage::Geometry:
+ if (lang_.gles) {
+ C("precision highp float;\n");
+ }
+ break;
default:
break;
}
@@ -172,7 +209,7 @@ void ShaderWriter::BeginVSMain(Slice inputs, Slice uniform
F(" vec4 pos : %s;\n", lang_.shaderLanguage == HLSL_D3D11 ? "SV_Position" : "POSITION");
C("};\n");
- C("VS_OUTPUT main( "); // 2 spaces for the D3D9 rewind
+ C("VS_OUTPUT main( "); // 2 spaces for the rewind
if (lang_.shaderLanguage == HLSL_D3D11) {
C("uint gl_VertexIndex : SV_VertexID, ");
}
@@ -308,6 +345,43 @@ void ShaderWriter::BeginFSMain(Slice uniforms, Slice var
}
}
+void ShaderWriter::BeginGSMain(Slice varyings, Slice outVaryings) {
+ _assert_(this->stage_ == ShaderStage::Geometry);
+ switch (lang_.shaderLanguage) {
+ case HLSL_D3D11:
+ // Untested, but should work.
+ C("\nstruct GS_OUTPUT {\n");
+ for (auto &varying : outVaryings) {
+ F(" %s %s : %s;\n", varying.type, varying.name, semanticNames[varying.semantic]);
+ }
+ F(" vec4 pos : %s;\n", lang_.shaderLanguage == HLSL_D3D11 ? "SV_Position" : "POSITION");
+ C("};\n");
+ C("#define EmitVertex() emit.Append(gsout)\n");
+
+ C("void main(");
+ for (auto &varying : varyings) {
+ F(" in %s %s : %s, ", varying.type, varying.name, semanticNames[varying.semantic]);
+ }
+ C("inout TriangleStream emit) {\n");
+ C(" GS_OUTPUT gsout;\n");
+ break;
+ case GLSL_VULKAN:
+ for (auto &varying : varyings) {
+ F("layout(location = %d) %s in %s %s[]; // %s\n", varying.index, varying.precision ? varying.precision : "", varying.type, varying.name, semanticNames[varying.semantic]);
+ }
+ for (auto &varying : outVaryings) {
+ F("layout(location = %d) %s out %s %s; // %s\n", varying.index, varying.precision ? varying.precision : "", varying.type, varying.name, semanticNames[varying.semantic]);
+ }
+ C("\nvoid main() {\n");
+ break;
+ case GLSL_3xx:
+ C("\nvoid main() {\n");
+ break;
+ default:
+ break;
+ }
+}
+
void ShaderWriter::EndVSMain(Slice varyings) {
_assert_(this->stage_ == ShaderStage::Vertex);
switch (lang_.shaderLanguage) {
@@ -349,6 +423,11 @@ void ShaderWriter::EndFSMain(const char *vec4_color_variable, FSFlags flags) {
C("}\n");
}
+void ShaderWriter::EndGSMain() {
+ _assert_(this->stage_ == ShaderStage::Geometry);
+ C("}\n");
+}
+
void ShaderWriter::HighPrecisionFloat() {
if ((ShaderLanguageIsOpenGL(lang_.shaderLanguage) && lang_.gles) || lang_.shaderLanguage == GLSL_VULKAN) {
C("precision highp float;\n");
diff --git a/Common/GPU/ShaderWriter.h b/Common/GPU/ShaderWriter.h
index f6a04c828b..50f3bb1f0d 100644
--- a/Common/GPU/ShaderWriter.h
+++ b/Common/GPU/ShaderWriter.h
@@ -85,10 +85,12 @@ public:
// Simple shaders with no special tricks.
void BeginVSMain(Slice inputs, Slice uniforms, Slice varyings);
void BeginFSMain(Slice uniforms, Slice varyings, FSFlags flags);
+ void BeginGSMain(Slice varyings, Slice outVaryings);
// For simple shaders that output a single color, we can deal with this generically.
void EndVSMain(Slice varyings);
void EndFSMain(const char *vec4_color_variable, FSFlags flags);
+ void EndGSMain();
const ShaderLanguageDesc &Lang() const {
return lang_;
diff --git a/Common/GPU/Vulkan/VulkanBarrier.cpp b/Common/GPU/Vulkan/VulkanBarrier.cpp
index 125d51ea6d..e4f2d09089 100644
--- a/Common/GPU/Vulkan/VulkanBarrier.cpp
+++ b/Common/GPU/Vulkan/VulkanBarrier.cpp
@@ -4,7 +4,7 @@
void VulkanBarrier::Flush(VkCommandBuffer cmd) {
if (!imageBarriers_.empty()) {
- vkCmdPipelineBarrier(cmd, srcStageMask_, dstStageMask_, 0, 0, nullptr, 0, nullptr, (uint32_t)imageBarriers_.size(), imageBarriers_.data());
+ vkCmdPipelineBarrier(cmd, srcStageMask_, dstStageMask_, dependencyFlags_, 0, nullptr, 0, nullptr, (uint32_t)imageBarriers_.size(), imageBarriers_.data());
}
imageBarriers_.clear();
srcStageMask_ = 0;
diff --git a/Common/GPU/Vulkan/VulkanBarrier.h b/Common/GPU/Vulkan/VulkanBarrier.h
index eb949dd2f0..19edc548ba 100644
--- a/Common/GPU/Vulkan/VulkanBarrier.h
+++ b/Common/GPU/Vulkan/VulkanBarrier.h
@@ -19,8 +19,11 @@ public:
VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask,
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask
) {
+ _dbg_assert_(image != VK_NULL_HANDLE);
+
srcStageMask_ |= srcStageMask;
dstStageMask_ |= dstStageMask;
+ dependencyFlags_ |= VK_DEPENDENCY_BY_REGION_BIT;
VkImageMemoryBarrier imageBarrier;
imageBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
@@ -45,6 +48,8 @@ public:
void TransitionImageAuto(
VkImage image, int baseMip, int numMipLevels, VkImageAspectFlags aspectMask, VkImageLayout oldImageLayout, VkImageLayout newImageLayout
) {
+ _dbg_assert_(image != VK_NULL_HANDLE);
+
VkAccessFlags srcAccessMask = 0;
VkAccessFlags dstAccessMask = 0;
switch (oldImageLayout) {
@@ -112,4 +117,5 @@ private:
VkPipelineStageFlags srcStageMask_ = 0;
VkPipelineStageFlags dstStageMask_ = 0;
std::vector imageBarriers_;
+ VkDependencyFlags dependencyFlags_ = 0;
};
diff --git a/Common/GPU/Vulkan/VulkanContext.cpp b/Common/GPU/Vulkan/VulkanContext.cpp
index 55edc0f31f..c2140c5663 100644
--- a/Common/GPU/Vulkan/VulkanContext.cpp
+++ b/Common/GPU/Vulkan/VulkanContext.cpp
@@ -600,8 +600,7 @@ void VulkanContext::ChooseDevice(int physical_device) {
deviceFeatures_.enabled.samplerAnisotropy = deviceFeatures_.available.samplerAnisotropy;
deviceFeatures_.enabled.shaderClipDistance = deviceFeatures_.available.shaderClipDistance;
deviceFeatures_.enabled.shaderCullDistance = deviceFeatures_.available.shaderCullDistance;
- // For easy wireframe mode, someday.
- deviceFeatures_.enabled.fillModeNonSolid = deviceFeatures_.available.fillModeNonSolid;
+ deviceFeatures_.enabled.geometryShader = deviceFeatures_.available.geometryShader;
GetDeviceLayerExtensionList(nullptr, device_extension_properties_);
@@ -667,7 +666,10 @@ VkResult VulkanContext::CreateDevice() {
extensionsLookup_.KHR_create_renderpass2 = true;
extensionsLookup_.KHR_depth_stencil_resolve = EnableDeviceExtension(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
}
+
extensionsLookup_.EXT_shader_stencil_export = EnableDeviceExtension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
+ 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);
VkDeviceCreateInfo device_info{ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
device_info.queueCreateInfoCount = 1;
@@ -1263,10 +1265,10 @@ bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *sourceCode,
glslang::TProgram program;
const char *shaderStrings[1];
- TBuiltInResource Resources;
+ TBuiltInResource Resources{};
init_resources(Resources);
- int defaultVersion;
+ int defaultVersion = 0;
EShMessages messages;
EProfile profile;
diff --git a/Common/GPU/Vulkan/VulkanDebug.cpp b/Common/GPU/Vulkan/VulkanDebug.cpp
index d4052e970a..83b3d974c0 100644
--- a/Common/GPU/Vulkan/VulkanDebug.cpp
+++ b/Common/GPU/Vulkan/VulkanDebug.cpp
@@ -86,7 +86,6 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback(
} else {
WARN_LOG(G3D, "VKDEBUG: %s", msg.c_str());
}
-
// false indicates that layer should not bail-out of an
// API call that had validation failures. This may mean that the
// app dies inside the driver due to invalid parameter(s).
diff --git a/Common/GPU/Vulkan/VulkanFrameData.cpp b/Common/GPU/Vulkan/VulkanFrameData.cpp
new file mode 100644
index 0000000000..ab74478396
--- /dev/null
+++ b/Common/GPU/Vulkan/VulkanFrameData.cpp
@@ -0,0 +1,224 @@
+#include
+
+#include "VulkanFrameData.h"
+#include "Common/Log.h"
+#include "Common/StringUtils.h"
+
+void FrameData::Init(VulkanContext *vulkan, int index) {
+ this->index = index;
+ VkDevice device = vulkan->GetDevice();
+
+ VkCommandPoolCreateInfo cmd_pool_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
+ cmd_pool_info.queueFamilyIndex = vulkan->GetGraphicsQueueFamilyIndex();
+ cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
+ VkResult res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &cmdPoolInit);
+ _dbg_assert_(res == VK_SUCCESS);
+ res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &cmdPoolMain);
+ _dbg_assert_(res == VK_SUCCESS);
+
+ VkCommandBufferAllocateInfo cmd_alloc = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
+ cmd_alloc.commandPool = cmdPoolInit;
+ cmd_alloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ cmd_alloc.commandBufferCount = 1;
+ res = vkAllocateCommandBuffers(device, &cmd_alloc, &initCmd);
+ _dbg_assert_(res == VK_SUCCESS);
+ cmd_alloc.commandPool = cmdPoolMain;
+ res = vkAllocateCommandBuffers(device, &cmd_alloc, &mainCmd);
+ res = vkAllocateCommandBuffers(device, &cmd_alloc, &presentCmd);
+ _dbg_assert_(res == VK_SUCCESS);
+
+ vulkan->SetDebugName(initCmd, VK_OBJECT_TYPE_COMMAND_BUFFER, StringFromFormat("initCmd%d", index).c_str());
+ vulkan->SetDebugName(mainCmd, VK_OBJECT_TYPE_COMMAND_BUFFER, StringFromFormat("mainCmd%d", index).c_str());
+ vulkan->SetDebugName(presentCmd, VK_OBJECT_TYPE_COMMAND_BUFFER, StringFromFormat("presentCmd%d", index).c_str());
+
+ // Creating the frame fence with true so they can be instantly waited on the first frame
+ fence = vulkan->CreateFence(true);
+ vulkan->SetDebugName(fence, VK_OBJECT_TYPE_FENCE, StringFromFormat("fence%d", index).c_str());
+ readyForFence = true;
+
+ // This fence is used for synchronizing readbacks. Does not need preinitialization.
+ // TODO: Put this in frameDataShared, only one is needed.
+ readbackFence = vulkan->CreateFence(false);
+ vulkan->SetDebugName(fence, VK_OBJECT_TYPE_FENCE, "readbackFence");
+
+ VkQueryPoolCreateInfo query_ci{ VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO };
+ query_ci.queryCount = MAX_TIMESTAMP_QUERIES;
+ query_ci.queryType = VK_QUERY_TYPE_TIMESTAMP;
+ res = vkCreateQueryPool(device, &query_ci, nullptr, &profile.queryPool);
+}
+
+void FrameData::Destroy(VulkanContext *vulkan) {
+ VkDevice device = vulkan->GetDevice();
+ vkDestroyCommandPool(device, cmdPoolInit, nullptr);
+ vkDestroyCommandPool(device, cmdPoolMain, nullptr);
+ vkDestroyFence(device, fence, nullptr);
+ vkDestroyFence(device, readbackFence, nullptr);
+ vkDestroyQueryPool(device, profile.queryPool, nullptr);
+}
+
+void FrameData::AcquireNextImage(VulkanContext *vulkan, FrameDataShared &shared) {
+ _dbg_assert_(!hasAcquired);
+
+ // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on.
+ VkResult res = vkAcquireNextImageKHR(vulkan->GetDevice(), vulkan->GetSwapchain(), UINT64_MAX, shared.acquireSemaphore, (VkFence)VK_NULL_HANDLE, &curSwapchainImage);
+ switch (res) {
+ case VK_SUCCESS:
+ hasAcquired = true;
+ break;
+ case VK_SUBOPTIMAL_KHR:
+ hasAcquired = true;
+ // Hopefully the resize will happen shortly. Ignore - one frame might look bad or something.
+ WARN_LOG(G3D, "VK_SUBOPTIMAL_KHR returned - ignoring");
+ break;
+ case VK_ERROR_OUT_OF_DATE_KHR:
+ // We do not set hasAcquired here!
+ WARN_LOG(G3D, "VK_ERROR_OUT_OF_DATE_KHR returned from AcquireNextImage - processing the frame, but not presenting");
+ skipSwap = true;
+ break;
+ default:
+ // Weird, shouldn't get any other values. Maybe lost device?
+ _assert_msg_(false, "vkAcquireNextImageKHR failed! result=%s", VulkanResultToString(res));
+ break;
+ }
+}
+
+VkResult FrameData::QueuePresent(VulkanContext *vulkan, FrameDataShared &shared) {
+ _dbg_assert_(hasAcquired);
+ hasAcquired = false;
+ _dbg_assert_(!skipSwap);
+
+ VkSwapchainKHR swapchain = vulkan->GetSwapchain();
+ VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
+ present.swapchainCount = 1;
+ present.pSwapchains = &swapchain;
+ present.pImageIndices = &curSwapchainImage;
+ present.pWaitSemaphores = &shared.renderingCompleteSemaphore;
+ present.waitSemaphoreCount = 1;
+
+ return vkQueuePresentKHR(vulkan->GetGraphicsQueue(), &present);
+}
+
+VkCommandBuffer FrameData::GetInitCmd(VulkanContext *vulkan) {
+ if (!hasInitCommands) {
+ VkCommandBufferBeginInfo begin = {
+ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ nullptr,
+ VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
+ };
+ vkResetCommandPool(vulkan->GetDevice(), cmdPoolInit, 0);
+ VkResult res = vkBeginCommandBuffer(initCmd, &begin);
+ if (res != VK_SUCCESS) {
+ return VK_NULL_HANDLE;
+ }
+
+ // Good spot to reset the query pool.
+ vkCmdResetQueryPool(initCmd, profile.queryPool, 0, MAX_TIMESTAMP_QUERIES);
+ vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, profile.queryPool, 0);
+
+ hasInitCommands = true;
+ }
+ return initCmd;
+}
+
+void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, FrameDataShared &sharedData) {
+ VkCommandBuffer cmdBufs[2];
+ int numCmdBufs = 0;
+
+ VkFence fenceToTrigger = VK_NULL_HANDLE;
+
+ if (hasInitCommands) {
+ if (profilingEnabled_) {
+ // Pre-allocated query ID 1 - end of init cmdbuf.
+ vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, profile.queryPool, 1);
+ }
+
+ VkResult res = vkEndCommandBuffer(initCmd);
+ cmdBufs[numCmdBufs++] = initCmd;
+
+ _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (init)! result=%s", VulkanResultToString(res));
+ hasInitCommands = false;
+ }
+
+ if ((hasMainCommands || hasPresentCommands) && type == FrameSubmitType::Sync) {
+ fenceToTrigger = readbackFence;
+ }
+
+ if (hasMainCommands) {
+ VkResult res = vkEndCommandBuffer(mainCmd);
+ _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (main)! result=%s", VulkanResultToString(res));
+
+ cmdBufs[numCmdBufs++] = mainCmd;
+ hasMainCommands = false;
+ }
+
+ if (hasPresentCommands && type != FrameSubmitType::Pending) {
+ VkResult res = vkEndCommandBuffer(presentCmd);
+ _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (present)! result=%s", VulkanResultToString(res));
+
+ cmdBufs[numCmdBufs++] = presentCmd;
+ hasPresentCommands = false;
+
+ if (type == FrameSubmitType::Present) {
+ fenceToTrigger = fence;
+ }
+ }
+
+ if (!numCmdBufs && fenceToTrigger == VK_NULL_HANDLE) {
+ // Nothing to do.
+ return;
+ }
+
+ VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO };
+ VkPipelineStageFlags waitStage[1]{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+ if (type == FrameSubmitType::Present && !skipSwap) {
+ _dbg_assert_(hasAcquired);
+ submit_info.waitSemaphoreCount = 1;
+ submit_info.pWaitSemaphores = &sharedData.acquireSemaphore;
+ submit_info.pWaitDstStageMask = waitStage;
+ }
+ submit_info.commandBufferCount = (uint32_t)numCmdBufs;
+ submit_info.pCommandBuffers = cmdBufs;
+ if (type == FrameSubmitType::Present && !skipSwap) {
+ submit_info.signalSemaphoreCount = 1;
+ submit_info.pSignalSemaphores = &sharedData.renderingCompleteSemaphore;
+ }
+
+ VkResult res;
+ if (fenceToTrigger == fence) {
+ // The fence is waited on by the main thread, they are not allowed to access it simultaneously.
+ res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger);
+ std::lock_guard lock(fenceMutex);
+ readyForFence = true;
+ fenceCondVar.notify_one();
+ } else {
+ res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger);
+ }
+
+ if (res == VK_ERROR_DEVICE_LOST) {
+ _assert_msg_(false, "Lost the Vulkan device in vkQueueSubmit! If this happens again, switch Graphics Backend away from Vulkan");
+ } else {
+ _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (main)! result=%s", VulkanResultToString(res));
+ }
+
+ if (type == FrameSubmitType::Sync) {
+ // Hard stall of the GPU, not ideal, but necessary so the CPU has the contents of the readback.
+ vkWaitForFences(vulkan->GetDevice(), 1, &readbackFence, true, UINT64_MAX);
+ vkResetFences(vulkan->GetDevice(), 1, &readbackFence);
+ syncDone = true;
+ }
+}
+
+void FrameDataShared::Init(VulkanContext *vulkan) {
+ VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
+ semaphoreCreateInfo.flags = 0;
+ VkResult res = vkCreateSemaphore(vulkan->GetDevice(), &semaphoreCreateInfo, nullptr, &acquireSemaphore);
+ _dbg_assert_(res == VK_SUCCESS);
+ res = vkCreateSemaphore(vulkan->GetDevice(), &semaphoreCreateInfo, nullptr, &renderingCompleteSemaphore);
+ _dbg_assert_(res == VK_SUCCESS);
+}
+
+void FrameDataShared::Destroy(VulkanContext *vulkan) {
+ VkDevice device = vulkan->GetDevice();
+ vkDestroySemaphore(device, acquireSemaphore, nullptr);
+ vkDestroySemaphore(device, renderingCompleteSemaphore, nullptr);
+}
diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h
new file mode 100644
index 0000000000..88d4c185d2
--- /dev/null
+++ b/Common/GPU/Vulkan/VulkanFrameData.h
@@ -0,0 +1,93 @@
+#pragma once
+
+#include
+
+#include
+#include
+
+#include "Common/GPU/Vulkan/VulkanContext.h"
+
+enum {
+ MAX_TIMESTAMP_QUERIES = 128,
+};
+
+enum class VKRRunType {
+ PRESENT,
+ SYNC,
+ EXIT,
+};
+
+struct QueueProfileContext {
+ VkQueryPool queryPool;
+ std::vector timestampDescriptions;
+ std::string profileSummary;
+ double cpuStartTime;
+ double cpuEndTime;
+};
+
+struct FrameDataShared {
+ // Permanent objects
+ VkSemaphore acquireSemaphore = VK_NULL_HANDLE;
+ VkSemaphore renderingCompleteSemaphore = VK_NULL_HANDLE;
+
+ void Init(VulkanContext *vulkan);
+ void Destroy(VulkanContext *vulkan);
+};
+
+enum class FrameSubmitType {
+ Pending,
+ Sync,
+ Present,
+};
+
+// Per-frame data, round-robin so we can overlap submission with execution of the previous frame.
+struct FrameData {
+ bool skipSwap = false;
+
+ std::mutex fenceMutex;
+ std::condition_variable fenceCondVar;
+ bool readyForFence = true;
+
+ VkFence fence = VK_NULL_HANDLE;
+ VkFence readbackFence = VK_NULL_HANDLE; // Strictly speaking we might only need one global of these.
+
+ // These are on different threads so need separate pools.
+ VkCommandPool cmdPoolInit = VK_NULL_HANDLE; // Written to from main thread
+ VkCommandPool cmdPoolMain = VK_NULL_HANDLE; // Written to from render thread, which also submits
+
+ VkCommandBuffer initCmd = VK_NULL_HANDLE;
+ VkCommandBuffer mainCmd = VK_NULL_HANDLE;
+ VkCommandBuffer presentCmd = VK_NULL_HANDLE;
+
+ bool hasInitCommands = false;
+ bool hasMainCommands = false;
+ bool hasPresentCommands = false;
+
+ bool hasFencePending = false;
+ bool hasAcquired = false;
+
+ bool syncDone = false;
+
+ // Swapchain.
+ uint32_t curSwapchainImage = -1;
+
+ // Profiling.
+ QueueProfileContext profile;
+ bool profilingEnabled_ = false;
+
+ void Init(VulkanContext *vulkan, int index);
+ void Destroy(VulkanContext *vulkan);
+
+ void AcquireNextImage(VulkanContext *vulkan, FrameDataShared &shared);
+ VkResult QueuePresent(VulkanContext *vulkan, FrameDataShared &shared);
+
+ // Generally called from the main thread, unlike most of the rest.
+ VkCommandBuffer GetInitCmd(VulkanContext *vulkan);
+
+ // This will only submit if we are actually recording init commands.
+ void SubmitPending(VulkanContext *vulkan, FrameSubmitType type, FrameDataShared &shared);
+
+private:
+ // Metadata for logging etc
+ int index;
+};
diff --git a/Common/GPU/Vulkan/VulkanLoader.h b/Common/GPU/Vulkan/VulkanLoader.h
index 1f3d7d7704..b0c14570da 100644
--- a/Common/GPU/Vulkan/VulkanLoader.h
+++ b/Common/GPU/Vulkan/VulkanLoader.h
@@ -241,6 +241,8 @@ struct VulkanExtensions {
bool KHR_depth_stencil_resolve;
bool EXT_shader_stencil_export;
bool EXT_swapchain_colorspace;
+ bool ARM_rasterization_order_attachment_access;
+ bool EXT_fragment_shader_interlock;
// bool EXT_depth_range_unrestricted; // Allows depth outside [0.0, 1.0] in 32-bit float depth buffers.
};
diff --git a/Common/GPU/Vulkan/VulkanProfiler.cpp b/Common/GPU/Vulkan/VulkanProfiler.cpp
index 36305484da..fccea843b8 100644
--- a/Common/GPU/Vulkan/VulkanProfiler.cpp
+++ b/Common/GPU/Vulkan/VulkanProfiler.cpp
@@ -34,13 +34,13 @@ void VulkanProfiler::BeginFrame(VulkanContext *vulkan, VkCommandBuffer firstComm
static const char * const indent[4] = { "", " ", " ", " " };
if (!scopes_.empty()) {
- NOTICE_LOG(G3D, "Profiling events this frame:");
+ INFO_LOG(G3D, "Profiling events this frame:");
}
// Log it all out.
for (auto &scope : scopes_) {
if (scope.endQueryId == -1) {
- NOTICE_LOG(G3D, "Unclosed scope: %s", scope.name.c_str());
+ WARN_LOG(G3D, "Unclosed scope: %s", scope.name.c_str());
continue;
}
uint64_t startTime = results[scope.startQueryId];
@@ -50,7 +50,7 @@ void VulkanProfiler::BeginFrame(VulkanContext *vulkan, VkCommandBuffer firstComm
double milliseconds = (double)delta * timestampConversionFactor;
- NOTICE_LOG(G3D, "%s%s (%0.3f ms)", indent[scope.level & 3], scope.name.c_str(), milliseconds);
+ INFO_LOG(G3D, "%s%s (%0.3f ms)", indent[scope.level & 3], scope.name.c_str(), milliseconds);
}
scopes_.clear();
diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp
index 10a88e623a..69d81b5391 100644
--- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp
+++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp
@@ -30,13 +30,14 @@ static void MergeRenderAreaRectInto(VkRect2D *dest, VkRect2D &src) {
// We need to take the "max" of the features used in the two render passes.
RenderPassType MergeRPTypes(RenderPassType a, RenderPassType b) {
// Either both are backbuffer type, or neither are.
- _dbg_assert_((a == RP_TYPE_BACKBUFFER) == (b == RP_TYPE_BACKBUFFER));
- if (a == b) {
- // Trivial merging case.
+ // These can't merge with other renderpasses
+ if (a == RP_TYPE_BACKBUFFER || b == RP_TYPE_BACKBUFFER) {
+ _dbg_assert_(a == b);
return a;
}
- // More cases to be added later.
- return a;
+
+ // The rest we can just OR together to get the maximum feature set.
+ return (RenderPassType)((u32)a | (u32)b);
}
void VulkanQueueRunner::CreateDeviceObjects() {
@@ -138,6 +139,171 @@ void VulkanQueueRunner::DestroyDeviceObjects() {
renderPasses_.Clear();
}
+bool VulkanQueueRunner::CreateSwapchain(VkCommandBuffer cmdInit) {
+ VkResult res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &swapchainImageCount_, nullptr);
+ _dbg_assert_(res == VK_SUCCESS);
+
+ VkImage *swapchainImages = new VkImage[swapchainImageCount_];
+ res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &swapchainImageCount_, swapchainImages);
+ if (res != VK_SUCCESS) {
+ ERROR_LOG(G3D, "vkGetSwapchainImagesKHR failed");
+ delete[] swapchainImages;
+ return false;
+ }
+
+ for (uint32_t i = 0; i < swapchainImageCount_; i++) {
+ SwapchainImageData sc_buffer{};
+ sc_buffer.image = swapchainImages[i];
+
+ VkImageViewCreateInfo color_image_view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
+ color_image_view.format = vulkan_->GetSwapchainFormat();
+ color_image_view.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+ color_image_view.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+ color_image_view.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+ color_image_view.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+ color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ color_image_view.subresourceRange.baseMipLevel = 0;
+ color_image_view.subresourceRange.levelCount = 1;
+ color_image_view.subresourceRange.baseArrayLayer = 0;
+ color_image_view.subresourceRange.layerCount = 1;
+ color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ color_image_view.flags = 0;
+ color_image_view.image = sc_buffer.image;
+
+ // We leave the images as UNDEFINED, there's no need to pre-transition them as
+ // the backbuffer renderpass starts out with them being auto-transitioned from UNDEFINED anyway.
+ // Also, turns out it's illegal to transition un-acquired images, thanks Hans-Kristian. See #11417.
+
+ res = vkCreateImageView(vulkan_->GetDevice(), &color_image_view, nullptr, &sc_buffer.view);
+ swapchainImages_.push_back(sc_buffer);
+ _dbg_assert_(res == VK_SUCCESS);
+ }
+ delete[] swapchainImages;
+
+ // Must be before InitBackbufferRenderPass.
+ if (InitDepthStencilBuffer(cmdInit)) {
+ InitBackbufferFramebuffers(vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight());
+ }
+ return true;
+}
+
+
+bool VulkanQueueRunner::InitBackbufferFramebuffers(int width, int height) {
+ VkResult res;
+ // We share the same depth buffer but have multiple color buffers, see the loop below.
+ VkImageView attachments[2] = { VK_NULL_HANDLE, depth_.view };
+
+ VkFramebufferCreateInfo fb_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
+ fb_info.renderPass = GetCompatibleRenderPass()->Get(vulkan_, RP_TYPE_BACKBUFFER);
+ fb_info.attachmentCount = 2;
+ fb_info.pAttachments = attachments;
+ fb_info.width = width;
+ fb_info.height = height;
+ fb_info.layers = 1;
+
+ framebuffers_.resize(swapchainImageCount_);
+
+ for (uint32_t i = 0; i < swapchainImageCount_; i++) {
+ attachments[0] = swapchainImages_[i].view;
+ res = vkCreateFramebuffer(vulkan_->GetDevice(), &fb_info, nullptr, &framebuffers_[i]);
+ _dbg_assert_(res == VK_SUCCESS);
+ if (res != VK_SUCCESS) {
+ framebuffers_.clear();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool VulkanQueueRunner::InitDepthStencilBuffer(VkCommandBuffer cmd) {
+ const VkFormat depth_format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat;
+ int aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ VkImageCreateInfo image_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
+ image_info.imageType = VK_IMAGE_TYPE_2D;
+ image_info.format = depth_format;
+ image_info.extent.width = vulkan_->GetBackbufferWidth();
+ image_info.extent.height = vulkan_->GetBackbufferHeight();
+ image_info.extent.depth = 1;
+ image_info.mipLevels = 1;
+ image_info.arrayLayers = 1;
+ image_info.samples = VK_SAMPLE_COUNT_1_BIT;
+ image_info.queueFamilyIndexCount = 0;
+ image_info.pQueueFamilyIndices = nullptr;
+ image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ image_info.flags = 0;
+
+ depth_.format = depth_format;
+
+ VmaAllocationCreateInfo allocCreateInfo{};
+ VmaAllocationInfo allocInfo{};
+
+ allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+
+ VkResult res = vmaCreateImage(vulkan_->Allocator(), &image_info, &allocCreateInfo, &depth_.image, &depth_.alloc, &allocInfo);
+ _dbg_assert_(res == VK_SUCCESS);
+ if (res != VK_SUCCESS)
+ return false;
+
+ vulkan_->SetDebugName(depth_.image, VK_OBJECT_TYPE_IMAGE, "BackbufferDepth");
+
+ TransitionImageLayout2(cmd, depth_.image, 0, 1,
+ aspectMask,
+ VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+ VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
+ VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
+ 0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
+
+ VkImageViewCreateInfo depth_view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
+ depth_view_info.image = depth_.image;
+ depth_view_info.format = depth_format;
+ depth_view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+ depth_view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+ depth_view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+ depth_view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+ depth_view_info.subresourceRange.aspectMask = aspectMask;
+ depth_view_info.subresourceRange.baseMipLevel = 0;
+ depth_view_info.subresourceRange.levelCount = 1;
+ depth_view_info.subresourceRange.baseArrayLayer = 0;
+ depth_view_info.subresourceRange.layerCount = 1;
+ depth_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ depth_view_info.flags = 0;
+
+ VkDevice device = vulkan_->GetDevice();
+
+ res = vkCreateImageView(device, &depth_view_info, NULL, &depth_.view);
+ _dbg_assert_(res == VK_SUCCESS);
+ if (res != VK_SUCCESS)
+ return false;
+
+ return true;
+}
+
+
+void VulkanQueueRunner::DestroyBackBuffers() {
+ for (auto &image : swapchainImages_) {
+ vulkan_->Delete().QueueDeleteImageView(image.view);
+ }
+ swapchainImages_.clear();
+
+ if (depth_.view) {
+ vulkan_->Delete().QueueDeleteImageView(depth_.view);
+ }
+ if (depth_.image) {
+ _dbg_assert_(depth_.alloc);
+ vulkan_->Delete().QueueDeleteImageAllocation(depth_.image, depth_.alloc);
+ }
+ depth_ = {};
+ for (uint32_t i = 0; i < framebuffers_.size(); i++) {
+ _dbg_assert_(framebuffers_[i] != VK_NULL_HANDLE);
+ vulkan_->Delete().QueueDeleteFramebuffer(framebuffers_[i]);
+ }
+ framebuffers_.clear();
+
+ INFO_LOG(G3D, "Backbuffers destroyed");
+}
+
static VkAttachmentLoadOp ConvertLoadAction(VKRRenderPassLoadAction action) {
switch (action) {
case VKRRenderPassLoadAction::CLEAR: return VK_ATTACHMENT_LOAD_OP_CLEAR;
@@ -155,31 +321,40 @@ static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) {
return VK_ATTACHMENT_STORE_OP_DONT_CARE; // avoid compiler warning
}
-VkRenderPass CreateRP(VulkanContext *vulkan, const RPKey &key, RenderPassType rpType) {
+// Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827
+// 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) {
+ bool selfDependency = rpType == RP_TYPE_COLOR_INPUT || rpType == RP_TYPE_COLOR_DEPTH_INPUT;
+ bool isBackbuffer = rpType == RP_TYPE_BACKBUFFER;
+ bool hasDepth = rpType == RP_TYPE_BACKBUFFER || rpType == RP_TYPE_COLOR_DEPTH || rpType == RP_TYPE_COLOR_DEPTH_INPUT;
+
VkAttachmentDescription attachments[2] = {};
- attachments[0].format = rpType == RP_TYPE_BACKBUFFER ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM;
+ attachments[0].format = isBackbuffer ? vulkan->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM;
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[0].loadOp = ConvertLoadAction(key.colorLoadAction);
attachments[0].storeOp = ConvertStoreAction(key.colorStoreAction);
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
- attachments[0].initialLayout = rpType == RP_TYPE_BACKBUFFER ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
- attachments[0].finalLayout = rpType == RP_TYPE_BACKBUFFER ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attachments[0].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attachments[0].finalLayout = isBackbuffer ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments[0].flags = 0;
- attachments[1].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat;
- attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
- attachments[1].loadOp = ConvertLoadAction(key.depthLoadAction);
- attachments[1].storeOp = ConvertStoreAction(key.depthStoreAction);
- attachments[1].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction);
- attachments[1].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction);
- attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
- attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
- attachments[1].flags = 0;
+ if (hasDepth) {
+ attachments[1].format = vulkan->GetDeviceInfo().preferredDepthStencilFormat;
+ attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+ attachments[1].loadOp = ConvertLoadAction(key.depthLoadAction);
+ attachments[1].storeOp = ConvertStoreAction(key.depthStoreAction);
+ attachments[1].stencilLoadOp = ConvertLoadAction(key.stencilLoadAction);
+ attachments[1].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction);
+ attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attachments[1].flags = 0;
+ }
VkAttachmentReference color_reference{};
color_reference.attachment = 0;
- color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ color_reference.layout = selfDependency ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depth_reference{};
depth_reference.attachment = 1;
@@ -188,32 +363,56 @@ VkRenderPass CreateRP(VulkanContext *vulkan, const RPKey &key, RenderPassType rp
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.flags = 0;
- subpass.inputAttachmentCount = 0;
- subpass.pInputAttachments = nullptr;
+ if (selfDependency) {
+ subpass.inputAttachmentCount = 1;
+ subpass.pInputAttachments = &color_reference;
+ } else {
+ subpass.inputAttachmentCount = 0;
+ subpass.pInputAttachments = nullptr;
+ }
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_reference;
subpass.pResolveAttachments = nullptr;
- subpass.pDepthStencilAttachment = &depth_reference;
+ if (hasDepth) {
+ subpass.pDepthStencilAttachment = &depth_reference;
+ }
subpass.preserveAttachmentCount = 0;
subpass.pPreserveAttachments = nullptr;
// Not sure if this is really necessary.
- VkSubpassDependency dep{};
- dep.srcSubpass = VK_SUBPASS_EXTERNAL;
- dep.dstSubpass = 0;
- dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- dep.srcAccessMask = 0;
- dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ VkSubpassDependency deps[2]{};
+ size_t numDeps = 0;
VkRenderPassCreateInfo rp{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
- rp.attachmentCount = 2;
+ rp.attachmentCount = hasDepth ? 2 : 1;
rp.pAttachments = attachments;
rp.subpassCount = 1;
rp.pSubpasses = &subpass;
- if (rpType == RP_TYPE_BACKBUFFER) {
- rp.dependencyCount = 1;
- rp.pDependencies = &dep;
+
+ if (isBackbuffer) {
+ deps[numDeps].srcSubpass = VK_SUBPASS_EXTERNAL;
+ deps[numDeps].dstSubpass = 0;
+ deps[numDeps].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ deps[numDeps].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ deps[numDeps].srcAccessMask = 0;
+ deps[numDeps].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ 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;
}
VkRenderPass pass;
@@ -228,7 +427,7 @@ VkRenderPass VKRRenderPass::Get(VulkanContext *vulkan, RenderPassType rpType) {
// practical later when referring to it. Could change to on-demand if it feels motivated
// but I think the render pass objects are cheap.
if (!pass[(int)rpType]) {
- pass[(int)rpType] = CreateRP(vulkan, key_, (RenderPassType)rpType);
+ pass[(int)rpType] = CreateRenderPass(vulkan, key_, (RenderPassType)rpType);
}
return pass[(int)rpType];
}
@@ -246,6 +445,30 @@ 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,
+ 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 &steps) {
// Optimizes renderpasses, then sequences them.
// Planned optimizations:
@@ -321,23 +544,54 @@ void VulkanQueueRunner::PreprocessSteps(std::vector &steps) {
}
}
-void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &steps, QueueProfileContext *profile) {
+void VulkanQueueRunner::RunSteps(std::vector &steps, FrameData &frameData, FrameDataShared &frameDataShared) {
+ QueueProfileContext *profile = frameData.profilingEnabled_ ? &frameData.profile : nullptr;
+
if (profile)
profile->cpuStartTime = time_now_d();
bool emitLabels = vulkan_->Extensions().EXT_debug_utils;
+ VkCommandBuffer cmd = frameData.hasPresentCommands ? frameData.presentCmd : frameData.mainCmd;
+
for (size_t i = 0; i < steps.size(); i++) {
const VKRStep &step = *steps[i];
if (emitLabels) {
VkDebugUtilsLabelEXT labelInfo{ VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT };
- labelInfo.pLabelName = step.tag;
- vkCmdBeginDebugUtilsLabelEXT(cmd, &labelInfo);
+ char temp[128];
+ if (step.stepType == VKRStepType::RENDER && step.render.framebuffer) {
+ snprintf(temp, sizeof(temp), "%s: %s", step.tag, step.render.framebuffer->Tag());
+ labelInfo.pLabelName = temp;
+ } else {
+ labelInfo.pLabelName = step.tag;
+ }
+ vkCmdBeginDebugUtilsLabelEXT(frameData.mainCmd, &labelInfo);
}
switch (step.stepType) {
case VKRStepType::RENDER:
+ if (!step.render.framebuffer) {
+ frameData.SubmitPending(vulkan_, FrameSubmitType::Pending, frameDataShared);
+
+ // When stepping in the GE debugger, we can end up here multiple times in a "frame".
+ // So only acquire once.
+ if (!frameData.hasAcquired) {
+ frameData.AcquireNextImage(vulkan_, frameDataShared);
+ SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image);
+ }
+
+ if (!frameData.hasPresentCommands) {
+ // A RENDER step rendering to the backbuffer is normally the last step that happens in a frame,
+ // unless taking a screenshot, in which case there might be a READBACK_IMAGE after it.
+ // This is why we have to switch cmd to presentCmd, in this case.
+ VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
+ begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+ vkBeginCommandBuffer(frameData.presentCmd, &begin);
+ frameData.hasPresentCommands = true;
+ }
+ cmd = frameData.presentCmd;
+ }
PerformRenderPass(step, cmd);
break;
case VKRStepType::COPY:
@@ -368,10 +622,12 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st
// Deleting all in one go should be easier on the instruction cache than deleting
// them as we go - and easier to debug because we can look backwards in the frame.
- for (size_t i = 0; i < steps.size(); i++) {
- delete steps[i];
+ for (auto step : steps) {
+ delete step;
}
+ steps.clear();
+
if (profile)
profile->cpuEndTime = time_now_d();
}
@@ -627,10 +883,13 @@ std::string VulkanQueueRunner::StepToString(const VKRStep &step) const {
const char *renderCmd;
switch (step.render.renderPassType) {
case RP_TYPE_BACKBUFFER: renderCmd = "BACKBUF"; break;
- case RP_TYPE_COLOR_DEPTH: renderCmd = "RENDER"; break;
+ case RP_TYPE_COLOR: renderCmd = "RENDER"; break;
+ case RP_TYPE_COLOR_DEPTH: renderCmd = "RENDER_DEPTH"; break;
+ case RP_TYPE_COLOR_INPUT: renderCmd = "RENDER_INPUT"; break;
+ case RP_TYPE_COLOR_DEPTH_INPUT: renderCmd = "RENDER_DEPTH_INPUT"; break;
default: renderCmd = "N/A";
}
- snprintf(buffer, sizeof(buffer), "%s %s (draws: %d, %dx%d/%dx%d, fb: %p, )", renderCmd, step.tag, step.render.numDraws, actual_w, actual_h, w, h, step.render.framebuffer);
+ snprintf(buffer, sizeof(buffer), "%s %s %s (draws: %d, %dx%d/%dx%d)", renderCmd, step.tag, step.render.framebuffer ? step.render.framebuffer->Tag() : "", step.render.numDraws, actual_w, actual_h, w, h);
break;
}
case VKRStepType::COPY:
@@ -640,7 +899,7 @@ std::string VulkanQueueRunner::StepToString(const VKRStep &step) const {
snprintf(buffer, sizeof(buffer), "BLIT '%s' %s -> %s (%dx%d->%dx%d, %s)", step.tag, step.copy.src->Tag(), step.copy.dst->Tag(), step.blit.srcRect.extent.width, step.blit.srcRect.extent.height, step.blit.dstRect.extent.width, step.blit.dstRect.extent.height, AspectToString(step.blit.aspectMask));
break;
case VKRStepType::READBACK:
- snprintf(buffer, sizeof(buffer), "READBACK '%s' %s (%dx%d, %s)", step.tag, step.readback.src->Tag(), step.readback.srcRect.extent.width, step.readback.srcRect.extent.height, AspectToString(step.readback.aspectMask));
+ snprintf(buffer, sizeof(buffer), "READBACK '%s' %s (%dx%d, %s)", step.tag, step.readback.src ? step.readback.src->Tag() : "(backbuffer)", step.readback.srcRect.extent.width, step.readback.srcRect.extent.height, AspectToString(step.readback.aspectMask));
break;
case VKRStepType::READBACK_IMAGE:
snprintf(buffer, sizeof(buffer), "READBACK_IMAGE '%s' (%dx%d)", step.tag, step.readback_image.srcRect.extent.width, step.readback_image.srcRect.extent.height);
@@ -817,6 +1076,9 @@ 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;
@@ -903,7 +1165,7 @@ void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout
srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
break;
default:
- _dbg_assert_msg_(false, "GetRenderPass: Unexpected color layout %d", (int)colorLayout);
+ _dbg_assert_msg_(false, "TransitionToOptimal: Unexpected color layout %d", (int)colorLayout);
break;
}
recordBarrier->TransitionImage(
@@ -939,7 +1201,7 @@ void TransitionToOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayout
srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
break;
default:
- _dbg_assert_msg_(false, "GetRenderPass: Unexpected depth layout %d", (int)depthStencilLayout);
+ _dbg_assert_msg_(false, "TransitionToOptimal: Unexpected depth layout %d", (int)depthStencilLayout);
break;
}
recordBarrier->TransitionImage(
@@ -986,7 +1248,7 @@ void TransitionFromOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayou
// Nothing to do.
break;
default:
- _dbg_assert_msg_(false, "GetRenderPass: Unexpected final color layout %d", (int)colorLayout);
+ _dbg_assert_msg_(false, "TransitionFromOptimal: Unexpected final color layout %d", (int)colorLayout);
break;
}
barrier[0].oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
@@ -1025,7 +1287,7 @@ void TransitionFromOptimal(VkCommandBuffer cmd, VkImage colorImage, VkImageLayou
// Nothing to do.
break;
default:
- _dbg_assert_msg_(false, "GetRenderPass: Unexpected final depth layout %d", (int)depthStencilLayout);
+ _dbg_assert_msg_(false, "TransitionFromOptimal: Unexpected final depth layout %d", (int)depthStencilLayout);
break;
}
barrier[barrierCount].oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
@@ -1057,7 +1319,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
iter.targetLayout
);
iter.fb->color.layout = iter.targetLayout;
- } else if ((iter.aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) && iter.fb->depth.layout != iter.targetLayout) {
+ } else if (iter.fb->depth.image != VK_NULL_HANDLE && (iter.aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) && iter.fb->depth.layout != iter.targetLayout) {
recordBarrier_.TransitionImageAuto(
iter.fb->depth.image,
0,
@@ -1070,7 +1332,6 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
}
}
-
// Don't execute empty renderpasses that keep the contents.
if (step.commands.empty() && step.render.colorLoad == VKRRenderPassLoadAction::KEEP && step.render.depthLoad == VKRRenderPassLoadAction::KEEP && step.render.stencilLoad == VKRRenderPassLoadAction::KEEP) {
// Flush the pending barrier
@@ -1087,7 +1348,6 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
int n = 0;
int stage = 0;
- VkImageMemoryBarrier barriers[2]{};
if (step.render.framebuffer->color.layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
recordBarrier_.TransitionImage(
step.render.framebuffer->color.image,
@@ -1102,7 +1362,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
);
}
- if (step.render.framebuffer->depth.layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
+ if (step.render.framebuffer->depth.image != VK_NULL_HANDLE && step.render.framebuffer->depth.layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
recordBarrier_.TransitionImage(
step.render.framebuffer->depth.image,
0,
@@ -1120,6 +1380,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
// This reads the layout of the color and depth images, and chooses a render pass using them that
// will transition to the desired final layout.
+ //
// NOTE: Flushes recordBarrier_.
VKRRenderPass *renderPass = PerformBindFramebufferAsRenderTarget(step, cmd);
@@ -1235,6 +1496,15 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
break;
}
+ case VKRRenderCommand::SELF_DEPENDENCY_BARRIER:
+ {
+ _assert_(step.render.pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT);
+ VulkanBarrier barrier;
+ SelfDependencyBarrier(step.render.framebuffer->color, VK_IMAGE_ASPECT_COLOR_BIT, &barrier);
+ barrier.Flush(cmd);
+ break;
+ }
+
case VKRRenderCommand::PUSH_CONSTANTS:
vkCmdPushConstants(cmd, pipelineLayout, c.push.stages, c.push.offset, c.push.size, c.push.data);
break;
@@ -1327,6 +1597,9 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR
VkFramebuffer framebuf;
int w;
int h;
+
+ bool hasDepth = RenderPassTypeHasDepth(step.render.renderPassType);
+
if (step.render.framebuffer) {
_dbg_assert_(step.render.finalColorLayout != VK_IMAGE_LAYOUT_UNDEFINED);
_dbg_assert_(step.render.finalDepthStencilLayout != VK_IMAGE_LAYOUT_UNDEFINED);
@@ -1367,7 +1640,7 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR
Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor);
numClearVals = 1;
}
- if (step.render.depthLoad == VKRRenderPassLoadAction::CLEAR || step.render.stencilLoad == VKRRenderPassLoadAction::CLEAR) {
+ if (hasDepth && (step.render.depthLoad == VKRRenderPassLoadAction::CLEAR || step.render.stencilLoad == VKRRenderPassLoadAction::CLEAR)) {
clearVal[1].depthStencil.depth = step.render.clearDepth;
clearVal[1].depthStencil.stencil = step.render.clearStencil;
numClearVals = 2;
@@ -1386,7 +1659,7 @@ VKRRenderPass *VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKR
h = vulkan_->GetBackbufferHeight();
Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor);
- numClearVals = 2; // We don't bother with a depth buffer here.
+ numClearVals = hasDepth ? 2 : 1; // We might do depth-less backbuffer in the future, though doubtful of the value.
clearVal[1].depthStencil.depth = 0.0f;
clearVal[1].depthStencil.stencil = 0;
}
@@ -1457,6 +1730,10 @@ void VulkanQueueRunner::PerformCopy(const VKRStep &step, VkCommandBuffer cmd) {
if (dst->depth.layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
SetupTransitionToTransferDst(dst->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
_dbg_assert_(dst->depth.layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+ } else {
+ // Kingdom Hearts: Subsequent copies to the same depth buffer without any other use.
+ // Not super sure how that happens, but we need a barrier to pass sync validation.
+ SetupTransferDstWriteAfterWrite(dst->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
}
}
@@ -1468,6 +1745,8 @@ void VulkanQueueRunner::PerformCopy(const VKRStep &step, VkCommandBuffer cmd) {
vkCmdCopyImage(cmd, src->color.image, src->color.layout, dst->color.image, dst->color.layout, 1, ©);
}
if (step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
+ _dbg_assert_(src->depth.image != VK_NULL_HANDLE);
+ _dbg_assert_(dst->depth.image != VK_NULL_HANDLE);
copy.srcSubresource.aspectMask = step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
copy.dstSubresource.aspectMask = step.copy.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
vkCmdCopyImage(cmd, src->depth.image, src->depth.layout, dst->depth.image, dst->depth.layout, 1, ©);
@@ -1478,9 +1757,6 @@ void VulkanQueueRunner::PerformBlit(const VKRStep &step, VkCommandBuffer cmd) {
// The barrier code doesn't handle this case. We'd need to transition to GENERAL to do an intra-image copy.
_dbg_assert_(step.blit.src != step.blit.dst);
- VkImageMemoryBarrier srcBarriers[2]{};
- VkImageMemoryBarrier dstBarriers[2]{};
-
VKRFramebuffer *src = step.blit.src;
VKRFramebuffer *dst = step.blit.dst;
@@ -1522,6 +1798,9 @@ void VulkanQueueRunner::PerformBlit(const VKRStep &step, VkCommandBuffer cmd) {
// We can't copy only depth or only stencil unfortunately.
if (step.blit.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
+ _dbg_assert_(src->depth.image != VK_NULL_HANDLE);
+ _dbg_assert_(dst->depth.image != VK_NULL_HANDLE);
+
if (src->depth.layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
SetupTransitionToTransferSrc(src->depth, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, &recordBarrier_);
}
@@ -1652,6 +1931,33 @@ void VulkanQueueRunner::SetupTransitionToTransferDst(VKRImage &img, VkImageAspec
img.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
}
+void VulkanQueueRunner::SetupTransferDstWriteAfterWrite(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier) {
+ VkImageAspectFlags imageAspect = aspect;
+ VkAccessFlags srcAccessMask = 0;
+ VkPipelineStageFlags srcStageMask = 0;
+ if (img.format == VK_FORMAT_D16_UNORM_S8_UINT || img.format == VK_FORMAT_D24_UNORM_S8_UINT || img.format == VK_FORMAT_D32_SFLOAT_S8_UINT) {
+ // Barrier must specify both for combined depth/stencil buffers.
+ imageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ } else {
+ imageAspect = aspect;
+ }
+ _dbg_assert_(img.layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+ srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
+ recordBarrier->TransitionImage(
+ img.image,
+ 0,
+ 1,
+ aspect,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT
+ );
+}
+
void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd) {
ResizeReadbackBuffer(sizeof(uint32_t) * step.readback.srcRect.extent.width * step.readback.srcRect.extent.height);
diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.h b/Common/GPU/Vulkan/VulkanQueueRunner.h
index adb9e7d96e..7036029149 100644
--- a/Common/GPU/Vulkan/VulkanQueueRunner.h
+++ b/Common/GPU/Vulkan/VulkanQueueRunner.h
@@ -8,6 +8,7 @@
#include "Common/Data/Collections/Hashmaps.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanBarrier.h"
+#include "Common/GPU/Vulkan/VulkanFrameData.h"
#include "Common/Data/Convert/SmallDataConvert.h"
#include "Common/Data/Collections/TinySet.h"
#include "Common/GPU/DataFormat.h"
@@ -16,11 +17,11 @@ class VKRFramebuffer;
struct VKRGraphicsPipeline;
struct VKRComputePipeline;
struct VKRImage;
+struct FrameData;
enum {
QUEUE_HACK_MGS2_ACID = 1,
QUEUE_HACK_SONIC = 2,
- // Killzone PR = 4.
QUEUE_HACK_RENDERPASS_MERGE = 8,
};
@@ -36,24 +37,43 @@ enum class VKRRenderCommand : uint8_t {
DRAW,
DRAW_INDEXED,
PUSH_CONSTANTS,
+ SELF_DEPENDENCY_BARRIER,
NUM_RENDER_COMMANDS,
};
-enum PipelineFlags {
- PIPELINE_FLAG_NONE = 0,
- PIPELINE_FLAG_USES_LINES = (1 << 2),
- PIPELINE_FLAG_USES_BLEND_CONSTANT = (1 << 3),
- PIPELINE_FLAG_USES_DEPTH_STENCIL = (1 << 4), // Reads or writes the depth buffer.
+enum class PipelineFlags {
+ 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),
};
+ENUM_CLASS_BITOPS(PipelineFlags);
// Pipelines need to be created for the right type of render pass.
enum RenderPassType {
- RP_TYPE_BACKBUFFER,
+ // These four are organized so that bit 0 is DEPTH and bit 1 is INPUT, so
+ // they can be OR-ed together in MergeRPTypes.
+ RP_TYPE_COLOR,
RP_TYPE_COLOR_DEPTH,
+ RP_TYPE_COLOR_INPUT,
+ RP_TYPE_COLOR_DEPTH_INPUT,
+
+ // This is the odd one out, and gets special handling in MergeRPTypes.
+ RP_TYPE_BACKBUFFER, // For the backbuffer we can always use CLEAR/DONT_CARE, so bandwidth cost for a depth channel is negligible.
+
// Later will add pure-color render passes.
RP_TYPE_COUNT,
};
+inline bool RenderPassTypeHasDepth(RenderPassType type) {
+ return type == RP_TYPE_BACKBUFFER || type == RP_TYPE_COLOR_DEPTH || type == RP_TYPE_COLOR_DEPTH_INPUT;
+}
+
+inline bool RenderPassTypeHasInput(RenderPassType type) {
+ return type == RP_TYPE_COLOR_INPUT || type == RP_TYPE_COLOR_DEPTH_INPUT;
+}
+
struct VkRenderData {
VKRRenderCommand cmd;
union {
@@ -144,14 +164,10 @@ struct TransitionRequest {
VKRFramebuffer *fb;
VkImageAspectFlags aspect; // COLOR or DEPTH
VkImageLayout targetLayout;
-};
-struct QueueProfileContext {
- VkQueryPool queryPool;
- std::vector timestampDescriptions;
- std::string profileSummary;
- double cpuStartTime;
- double cpuEndTime;
+ bool operator == (const TransitionRequest &other) const {
+ return fb == other.fb && aspect == other.aspect && targetLayout == other.targetLayout;
+ }
};
class VKRRenderPass;
@@ -168,7 +184,6 @@ struct VKRStep {
union {
struct {
VKRFramebuffer *framebuffer;
- // TODO: Look these up through renderPass?
VKRRenderPassLoadAction colorLoad;
VKRRenderPassLoadAction depthLoad;
VKRRenderPassLoadAction stencilLoad;
@@ -183,7 +198,7 @@ struct VKRStep {
int numReads;
VkImageLayout finalColorLayout;
VkImageLayout finalDepthStencilLayout;
- u32 pipelineFlags;
+ PipelineFlags pipelineFlags; // contains the self dependency flag, in the form of USES_INPUT_ATTACHMENT
VkRect2D renderArea;
// Render pass type. Deduced after finishing recording the pass, from the used pipelines.
// NOTE: Storing the render pass here doesn't do much good, we change the compatible parameters (load/store ops) during step optimization.
@@ -245,6 +260,14 @@ private:
RPKey key_;
};
+// These are enqueued from the main thread,
+// and the render thread pops them off
+struct VKRRenderThreadTask {
+ std::vector steps;
+ int frame;
+ VKRRunType runType;
+};
+
class VulkanQueueRunner {
public:
VulkanQueueRunner(VulkanContext *vulkan) : vulkan_(vulkan), renderPasses_(16) {}
@@ -255,7 +278,7 @@ public:
}
void PreprocessSteps(std::vector &steps);
- void RunSteps(VkCommandBuffer cmd, std::vector &steps, QueueProfileContext *profile);
+ void RunSteps(std::vector &steps, FrameData &frameData, FrameDataShared &frameDataShared);
void LogSteps(const std::vector &steps, bool verbose);
std::string StepToString(const VKRStep &step) const;
@@ -263,6 +286,14 @@ public:
void CreateDeviceObjects();
void DestroyDeviceObjects();
+ // Swapchain
+ void DestroyBackBuffers();
+ bool CreateSwapchain(VkCommandBuffer cmdInit);
+
+ bool HasBackbuffers() const {
+ return !framebuffers_.empty();
+ }
+
// Get a render pass that's compatible with all our framebuffers.
// Note that it's precached, cannot look up in the map as this might be on another thread.
VKRRenderPass *GetCompatibleRenderPass() const {
@@ -302,6 +333,9 @@ public:
}
private:
+ bool InitBackbufferFramebuffers(int width, int height);
+ bool InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering.
+
VKRRenderPass *PerformBindFramebufferAsRenderTarget(const VKRStep &pass, VkCommandBuffer cmd);
void PerformRenderPass(const VKRStep &pass, VkCommandBuffer cmd);
void PerformCopy(const VKRStep &pass, VkCommandBuffer cmd);
@@ -323,6 +357,9 @@ private:
static void SetupTransitionToTransferSrc(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrier *recordBarrier);
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_;
@@ -354,4 +391,20 @@ private:
// Stored here to help reuse the allocation.
VulkanBarrier recordBarrier_;
+
+ // Swap chain management
+ struct SwapchainImageData {
+ VkImage image;
+ VkImageView view;
+ };
+ std::vector framebuffers_;
+ std::vector swapchainImages_;
+ uint32_t swapchainImageCount_ = 0;
+ struct DepthBufferInfo {
+ VkFormat format = VK_FORMAT_UNDEFINED;
+ VkImage image = VK_NULL_HANDLE;
+ VmaAllocation alloc = VK_NULL_HANDLE;
+ VkImageView view = VK_NULL_HANDLE;
+ };
+ DepthBufferInfo depth_;
};
diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp
index 10cc890009..3343bd889f 100644
--- a/Common/GPU/Vulkan/VulkanRenderManager.cpp
+++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp
@@ -14,7 +14,7 @@
#include "Common/Thread/ThreadUtil.h"
#if 0 // def _DEBUG
-#define VLOG(...) INFO_LOG(G3D, __VA_ARGS__)
+#define VLOG(...) NOTICE_LOG(G3D, __VA_ARGS__)
#else
#define VLOG(...)
#endif
@@ -30,30 +30,38 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleR
// Fill in the last part of the desc since now it's time to block.
VkShaderModule vs = desc->vertexShader->BlockUntilReady();
VkShaderModule fs = desc->fragmentShader->BlockUntilReady();
+ VkShaderModule gs = desc->geometryShader ? desc->geometryShader->BlockUntilReady() : VK_NULL_HANDLE;
- if (!vs || !fs) {
+ if (!vs || !fs || (!gs && desc->geometryShader)) {
ERROR_LOG(G3D, "Failed creating graphics pipeline - missing shader modules");
// We're kinda screwed here?
return false;
}
- VkPipelineShaderStageCreateInfo ss[2]{};
+ uint32_t stageCount = 2;
+ VkPipelineShaderStageCreateInfo ss[3]{};
ss[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
ss[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
ss[0].pSpecializationInfo = nullptr;
ss[0].module = vs;
ss[0].pName = "main";
- ss[0].flags = 0;
ss[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
ss[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
ss[1].pSpecializationInfo = nullptr;
ss[1].module = fs;
ss[1].pName = "main";
- ss[1].flags = 0;
+ if (gs) {
+ stageCount++;
+ ss[2].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ ss[2].stage = VK_SHADER_STAGE_GEOMETRY_BIT;
+ ss[2].pSpecializationInfo = nullptr;
+ ss[2].module = gs;
+ ss[2].pName = "main";
+ }
VkGraphicsPipelineCreateInfo pipe{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
pipe.pStages = ss;
- pipe.stageCount = 2;
+ pipe.stageCount = stageCount;
pipe.renderPass = compatibleRenderPass;
pipe.basePipelineIndex = 0;
pipe.pColorBlendState = &desc->cbs;
@@ -146,43 +154,71 @@ bool VKRComputePipeline::Create(VulkanContext *vulkan) {
return success;
}
-VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, const char *tag) : vulkan_(vk), tag_(tag) {
+VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, bool createDepthStencilBuffer, const char *tag) : vulkan_(vk), tag_(tag) {
width = _width;
height = _height;
+ _dbg_assert_(tag);
+
CreateImage(vulkan_, initCmd, color, width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag);
- CreateImage(vulkan_, initCmd, depth, width, height, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag);
+ if (createDepthStencilBuffer) {
+ CreateImage(vulkan_, initCmd, depth, width, height, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag);
+ }
+
+ UpdateTag(tag);
// We create the actual framebuffer objects on demand, because some combinations might not make sense.
+ // Framebuffer objects are just pointers to a set of images, so no biggie.
}
-VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPassType renderPassType) {
- if (framebuf[(int)renderPassType]) {
- return framebuf[(int)renderPassType];
+void VKRFramebuffer::UpdateTag(const char *newTag) {
+ char name[128];
+ snprintf(name, sizeof(name), "fb_color_%s", tag_.c_str());
+ vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, name);
+ vulkan_->SetDebugName(color.imageView, VK_OBJECT_TYPE_IMAGE_VIEW, name);
+ if (depth.image) {
+ snprintf(name, sizeof(name), "fb_depth_%s", tag_.c_str());
+ vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, name);
+ vulkan_->SetDebugName(depth.imageView, VK_OBJECT_TYPE_IMAGE_VIEW, name);
+ }
+ for (int rpType = 0; rpType < RP_TYPE_COUNT; rpType++) {
+ if (framebuf[rpType]) {
+ snprintf(name, sizeof(name), "fb_%s", tag_.c_str());
+ vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, name);
+ }
+ }
+}
+
+VkFramebuffer VKRFramebuffer::Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType) {
+ if (framebuf[(int)rpType]) {
+ return framebuf[(int)rpType];
}
VkFramebufferCreateInfo fbci{ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
VkImageView views[2]{};
- fbci.renderPass = compatibleRenderPass->Get(vulkan_, renderPassType);
- fbci.attachmentCount = 2;
- fbci.pAttachments = views;
+ bool hasDepth = rpType == RP_TYPE_BACKBUFFER || rpType == RP_TYPE_COLOR_DEPTH || rpType == RP_TYPE_COLOR_DEPTH_INPUT;
+
views[0] = color.imageView;
- views[1] = depth.imageView;
+ if (hasDepth) {
+ _dbg_assert_(depth.imageView != VK_NULL_HANDLE);
+ views[1] = depth.imageView;
+ }
+ fbci.renderPass = compatibleRenderPass->Get(vulkan_, rpType);
+ fbci.attachmentCount = hasDepth ? 2 : 1;
+ fbci.pAttachments = views;
fbci.width = width;
fbci.height = height;
fbci.layers = 1;
- VkResult res = vkCreateFramebuffer(vulkan_->GetDevice(), &fbci, nullptr, &framebuf[(int)renderPassType]);
+ VkResult res = vkCreateFramebuffer(vulkan_->GetDevice(), &fbci, nullptr, &framebuf[(int)rpType]);
_assert_(res == VK_SUCCESS);
if (!tag_.empty() && vulkan_->Extensions().EXT_debug_utils) {
- vulkan_->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_color_%s", tag_.c_str()).c_str());
- vulkan_->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_depth_%s", tag_.c_str()).c_str());
- vulkan_->SetDebugName(framebuf[(int)renderPassType], VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag_.c_str()).c_str());
+ vulkan_->SetDebugName(framebuf[(int)rpType], VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag_.c_str()).c_str());
}
- return framebuf[(int)renderPassType];
+ return framebuf[(int)rpType];
}
VKRFramebuffer::~VKRFramebuffer() {
@@ -221,7 +257,7 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int
// Strictly speaking we don't yet need VK_IMAGE_USAGE_SAMPLED_BIT for depth buffers since we do not yet sample depth buffers.
ici.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
if (color) {
- ici.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ ici.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
} else {
ici.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
}
@@ -286,53 +322,15 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int
}
VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan) : vulkan_(vulkan), queueRunner_(vulkan) {
- VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
- semaphoreCreateInfo.flags = 0;
- VkResult res = vkCreateSemaphore(vulkan_->GetDevice(), &semaphoreCreateInfo, nullptr, &acquireSemaphore_);
- _dbg_assert_(res == VK_SUCCESS);
- res = vkCreateSemaphore(vulkan_->GetDevice(), &semaphoreCreateInfo, nullptr, &renderingCompleteSemaphore_);
- _dbg_assert_(res == VK_SUCCESS);
-
inflightFramesAtStart_ = vulkan_->GetInflightFrames();
+
+ frameDataShared_.Init(vulkan);
+
for (int i = 0; i < inflightFramesAtStart_; i++) {
- VkCommandPoolCreateInfo cmd_pool_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
- cmd_pool_info.queueFamilyIndex = vulkan_->GetGraphicsQueueFamilyIndex();
- cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
- VkResult res = vkCreateCommandPool(vulkan_->GetDevice(), &cmd_pool_info, nullptr, &frameData_[i].cmdPoolInit);
- _dbg_assert_(res == VK_SUCCESS);
- res = vkCreateCommandPool(vulkan_->GetDevice(), &cmd_pool_info, nullptr, &frameData_[i].cmdPoolMain);
- _dbg_assert_(res == VK_SUCCESS);
-
- VkCommandBufferAllocateInfo cmd_alloc = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
- cmd_alloc.commandPool = frameData_[i].cmdPoolInit;
- cmd_alloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
- cmd_alloc.commandBufferCount = 1;
-
- res = vkAllocateCommandBuffers(vulkan_->GetDevice(), &cmd_alloc, &frameData_[i].initCmd);
- _dbg_assert_(res == VK_SUCCESS);
- cmd_alloc.commandPool = frameData_[i].cmdPoolMain;
- res = vkAllocateCommandBuffers(vulkan_->GetDevice(), &cmd_alloc, &frameData_[i].mainCmd);
- _dbg_assert_(res == VK_SUCCESS);
-
- // Creating the frame fence with true so they can be instantly waited on the first frame
- frameData_[i].fence = vulkan_->CreateFence(true);
-
- // This fence one is used for synchronizing readbacks. Does not need preinitialization.
- frameData_[i].readbackFence = vulkan_->CreateFence(false);
-
- VkQueryPoolCreateInfo query_ci{ VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO };
- query_ci.queryCount = MAX_TIMESTAMP_QUERIES;
- query_ci.queryType = VK_QUERY_TYPE_TIMESTAMP;
- res = vkCreateQueryPool(vulkan_->GetDevice(), &query_ci, nullptr, &frameData_[i].profile.queryPool);
+ frameData_[i].Init(vulkan, i);
}
queueRunner_.CreateDeviceObjects();
-
- // AMD hack for issue #10097 (older drivers only.)
- const auto &props = vulkan_->GetPhysicalDeviceProperties().properties;
- if (props.vendorID == VULKAN_VENDOR_AMD && props.apiVersion < VK_API_VERSION_1_1) {
- useThread_ = false;
- }
}
bool VulkanRenderManager::CreateBackbuffers() {
@@ -340,52 +338,14 @@ bool VulkanRenderManager::CreateBackbuffers() {
ERROR_LOG(G3D, "No swapchain - can't create backbuffers");
return false;
}
- VkResult res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &swapchainImageCount_, nullptr);
- _dbg_assert_(res == VK_SUCCESS);
- VkImage *swapchainImages = new VkImage[swapchainImageCount_];
- res = vkGetSwapchainImagesKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), &swapchainImageCount_, swapchainImages);
- if (res != VK_SUCCESS) {
- ERROR_LOG(G3D, "vkGetSwapchainImagesKHR failed");
- delete[] swapchainImages;
- return false;
- }
VkCommandBuffer cmdInit = GetInitCmd();
- for (uint32_t i = 0; i < swapchainImageCount_; i++) {
- SwapchainImageData sc_buffer{};
- sc_buffer.image = swapchainImages[i];
-
- VkImageViewCreateInfo color_image_view = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
- color_image_view.format = vulkan_->GetSwapchainFormat();
- color_image_view.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
- color_image_view.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
- color_image_view.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
- color_image_view.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
- color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
- color_image_view.subresourceRange.baseMipLevel = 0;
- color_image_view.subresourceRange.levelCount = 1;
- color_image_view.subresourceRange.baseArrayLayer = 0;
- color_image_view.subresourceRange.layerCount = 1;
- color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
- color_image_view.flags = 0;
- color_image_view.image = sc_buffer.image;
-
- // We leave the images as UNDEFINED, there's no need to pre-transition them as
- // the backbuffer renderpass starts out with them being auto-transitioned from UNDEFINED anyway.
- // Also, turns out it's illegal to transition un-acquired images, thanks Hans-Kristian. See #11417.
-
- res = vkCreateImageView(vulkan_->GetDevice(), &color_image_view, nullptr, &sc_buffer.view);
- swapchainImages_.push_back(sc_buffer);
- _dbg_assert_(res == VK_SUCCESS);
+ if (!queueRunner_.CreateSwapchain(cmdInit)) {
+ return false;
}
- delete[] swapchainImages;
- // Must be before InitBackbufferRenderPass.
- if (InitDepthStencilBuffer(cmdInit)) {
- InitBackbufferFramebuffers(vulkan_->GetBackbufferWidth(), vulkan_->GetBackbufferHeight());
- }
curWidthRaw_ = -1;
curHeightRaw_ = -1;
@@ -402,11 +362,10 @@ bool VulkanRenderManager::CreateBackbuffers() {
outOfDateFrames_ = 0;
// Start the thread.
- if (useThread_ && HasBackbuffers()) {
- run_ = true;
- // Won't necessarily be 0.
- threadInitFrame_ = vulkan_->GetCurFrame();
- INFO_LOG(G3D, "Starting Vulkan submission thread (threadInitFrame_ = %d)", vulkan_->GetCurFrame());
+ if (HasBackbuffers()) {
+ run_ = true; // For controlling the compiler thread's exit
+
+ INFO_LOG(G3D, "Starting Vulkan submission thread");
thread_ = std::thread(&VulkanRenderManager::ThreadFunc, this);
INFO_LOG(G3D, "Starting Vulkan compiler thread");
compileThread_ = std::thread(&VulkanRenderManager::CompileThreadFunc, this);
@@ -414,58 +373,58 @@ bool VulkanRenderManager::CreateBackbuffers() {
return true;
}
+// Called from main thread.
void VulkanRenderManager::StopThread() {
- if (useThread_ && run_) {
- run_ = false;
- // Stop the thread.
- for (int i = 0; i < vulkan_->GetInflightFrames(); i++) {
- auto &frameData = frameData_[i];
- {
- std::unique_lock lock(frameData.push_mutex);
- frameData.push_condVar.notify_all();
- }
- {
- std::unique_lock lock(frameData.pull_mutex);
- frameData.pull_condVar.notify_all();
- }
- // Zero the queries so we don't try to pull them later.
- frameData.profile.timestampDescriptions.clear();
+ {
+ // Tell the render thread to quit when it's done.
+ VKRRenderThreadTask task;
+ task.frame = vulkan_->GetCurFrame();
+ task.runType = VKRRunType::EXIT;
+ std::unique_lock lock(pushMutex_);
+ renderThreadQueue_.push(task);
+ pushCondVar_.notify_one();
+ }
+
+ // Compiler thread still relies on this.
+ run_ = false;
+
+ // Stop the thread.
+ thread_.join();
+
+ for (int i = 0; i < vulkan_->GetInflightFrames(); i++) {
+ auto &frameData = frameData_[i];
+ // Zero the queries so we don't try to pull them later.
+ frameData.profile.timestampDescriptions.clear();
+ }
+
+ INFO_LOG(G3D, "Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame());
+
+ compileCond_.notify_all();
+ compileThread_.join();
+ INFO_LOG(G3D, "Vulkan compiler thread joined.");
+
+ // Eat whatever has been queued up for this frame if anything.
+ Wipe();
+
+ // Clean out any remaining queued data, which might refer to things that might not be valid
+ // when we restart the thread...
+
+ // Not sure if this is still needed
+ for (int i = 0; i < vulkan_->GetInflightFrames(); i++) {
+ auto &frameData = frameData_[i];
+ if (frameData.hasInitCommands) {
+ // Clear 'em out. This can happen on restart sometimes.
+ vkEndCommandBuffer(frameData.initCmd);
+ frameData.hasInitCommands = false;
}
- thread_.join();
- INFO_LOG(G3D, "Vulkan submission thread joined. Frame=%d", vulkan_->GetCurFrame());
- compileCond_.notify_all();
- compileThread_.join();
- INFO_LOG(G3D, "Vulkan compiler thread joined.");
-
- // Eat whatever has been queued up for this frame if anything.
- Wipe();
-
- // Wait for any fences to finish and be resignaled, so we don't have sync issues.
- // Also clean out any queued data, which might refer to things that might not be valid
- // when we restart...
- for (int i = 0; i < vulkan_->GetInflightFrames(); i++) {
- auto &frameData = frameData_[i];
- _assert_(!frameData.readyForRun);
- _assert_(frameData.steps.empty());
- if (frameData.hasInitCommands) {
- // Clear 'em out. This can happen on restart sometimes.
- vkEndCommandBuffer(frameData.initCmd);
- frameData.hasInitCommands = false;
- }
- frameData.readyForRun = false;
- for (size_t i = 0; i < frameData.steps.size(); i++) {
- delete frameData.steps[i];
- }
- frameData.steps.clear();
-
- std::unique_lock lock(frameData.push_mutex);
- while (!frameData.readyForFence) {
- VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (stop)", i);
- frameData.push_condVar.wait(lock);
- }
+ if (frameData.hasMainCommands) {
+ vkEndCommandBuffer(frameData.mainCmd);
+ frameData.hasMainCommands = false;
+ }
+ if (frameData.hasPresentCommands) {
+ vkEndCommandBuffer(frameData.presentCmd);
+ frameData.hasPresentCommands = false;
}
- } else {
- INFO_LOG(G3D, "Vulkan submission thread was already stopped.");
}
}
@@ -473,45 +432,21 @@ void VulkanRenderManager::DestroyBackbuffers() {
StopThread();
vulkan_->WaitUntilQueueIdle();
- for (auto &image : swapchainImages_) {
- vulkan_->Delete().QueueDeleteImageView(image.view);
- }
- swapchainImages_.clear();
-
- if (depth_.view) {
- vulkan_->Delete().QueueDeleteImageView(depth_.view);
- }
- if (depth_.image) {
- _dbg_assert_(depth_.alloc);
- vulkan_->Delete().QueueDeleteImageAllocation(depth_.image, depth_.alloc);
- }
- depth_ = {};
- for (uint32_t i = 0; i < framebuffers_.size(); i++) {
- _dbg_assert_(framebuffers_[i] != VK_NULL_HANDLE);
- vulkan_->Delete().QueueDeleteFramebuffer(framebuffers_[i]);
- }
- framebuffers_.clear();
-
- INFO_LOG(G3D, "Backbuffers destroyed");
+ queueRunner_.DestroyBackBuffers();
}
VulkanRenderManager::~VulkanRenderManager() {
INFO_LOG(G3D, "VulkanRenderManager destructor");
- StopThread();
+
+ _dbg_assert_(!run_); // StopThread should already have been called from DestroyBackbuffers.
+
vulkan_->WaitUntilQueueIdle();
DrainCompileQueue();
VkDevice device = vulkan_->GetDevice();
- vkDestroySemaphore(device, acquireSemaphore_, nullptr);
- vkDestroySemaphore(device, renderingCompleteSemaphore_, nullptr);
+ frameDataShared_.Destroy(vulkan_);
for (int i = 0; i < inflightFramesAtStart_; i++) {
- vkFreeCommandBuffers(device, frameData_[i].cmdPoolInit, 1, &frameData_[i].initCmd);
- vkFreeCommandBuffers(device, frameData_[i].cmdPoolMain, 1, &frameData_[i].mainCmd);
- vkDestroyCommandPool(device, frameData_[i].cmdPoolInit, nullptr);
- vkDestroyCommandPool(device, frameData_[i].cmdPoolMain, nullptr);
- vkDestroyFence(device, frameData_[i].fence, nullptr);
- vkDestroyFence(device, frameData_[i].readbackFence, nullptr);
- vkDestroyQueryPool(device, frameData_[i].profile.queryPool, nullptr);
+ frameData_[i].Destroy(vulkan_);
}
queueRunner_.DestroyDeviceObjects();
}
@@ -532,7 +467,9 @@ void VulkanRenderManager::CompileThreadFunc() {
break;
}
- INFO_LOG(G3D, "Compilation thread has %d pipelines to create", (int)toCompile.size());
+ if (!toCompile.empty()) {
+ INFO_LOG(G3D, "Compilation thread has %d pipelines to create", (int)toCompile.size());
+ }
// TODO: Here we can sort the pending pipelines by vertex and fragment shaders,
// and split up further.
@@ -561,45 +498,30 @@ void VulkanRenderManager::DrainCompileQueue() {
void VulkanRenderManager::ThreadFunc() {
SetCurrentThreadName("RenderMan");
- int threadFrame = threadInitFrame_;
- bool nextFrame = false;
- bool firstFrame = true;
while (true) {
+ // Pop a task of the queue and execute it.
+ VKRRenderThreadTask task;
{
- if (nextFrame) {
- threadFrame++;
- if (threadFrame >= vulkan_->GetInflightFrames())
- threadFrame = 0;
+ std::unique_lock lock(pushMutex_);
+ while (renderThreadQueue_.empty()) {
+ pushCondVar_.wait(lock);
}
- FrameData &frameData = frameData_[threadFrame];
- std::unique_lock lock(frameData.pull_mutex);
- while (!frameData.readyForRun && run_) {
- VLOG("PULL: Waiting for frame[%d].readyForRun", threadFrame);
- frameData.pull_condVar.wait(lock);
- }
- if (!frameData.readyForRun && !run_) {
- // This means we're out of frames to render and run_ is false, so bail.
- break;
- }
- VLOG("PULL: frame[%d].readyForRun = false", threadFrame);
- frameData.readyForRun = false;
- // Previously we had a quick exit here that avoided calling Run() if run_ was suddenly false,
- // but that created a race condition where frames could end up not finished properly on resize etc.
+ task = renderThreadQueue_.front();
+ renderThreadQueue_.pop();
+ }
- // Only increment next time if we're done.
- nextFrame = frameData.type == VKRRunType::END;
- _dbg_assert_(frameData.type == VKRRunType::END || frameData.type == VKRRunType::SYNC);
+ // 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) {
+ // Oh, host wanted out. Let's leave.
+ break;
}
- VLOG("PULL: Running frame %d", threadFrame);
- if (firstFrame) {
- INFO_LOG(G3D, "Running first frame (%d)", threadFrame);
- firstFrame = false;
- }
- Run(threadFrame);
- VLOG("PULL: Finished frame %d", threadFrame);
+
+ Run(task);
}
// Wait for the device to be done with everything, before tearing stuff down.
+ // TODO: Do we need this?
vkDeviceWaitIdle(vulkan_->GetDevice());
VLOG("PULL: Quitting");
@@ -612,18 +534,20 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
int curFrame = vulkan_->GetCurFrame();
FrameData &frameData = frameData_[curFrame];
- // Make sure the very last command buffer from the frame before the previous has been fully executed.
- if (useThread_) {
- std::unique_lock lock(frameData.push_mutex);
+ VLOG("PUSH: Fencing %d", curFrame);
+
+ // Makes sure the submission from the previous time around has happened. Otherwise
+ // we are not allowed to wait from another thread here..
+ {
+ std::unique_lock lock(frameData.fenceMutex);
while (!frameData.readyForFence) {
- VLOG("PUSH: Waiting for frame[%d].readyForFence = 1", curFrame);
- frameData.push_condVar.wait(lock);
+ frameData.fenceCondVar.wait(lock);
}
frameData.readyForFence = false;
}
- VLOG("PUSH: Fencing %d", curFrame);
-
+ // This must be the very first Vulkan call we do in a new frame.
+ // Makes sure the very last command buffer from the frame before the previous has been fully executed.
if (vkWaitForFences(device, 1, &frameData.fence, true, UINT64_MAX) == VK_ERROR_DEVICE_LOST) {
_assert_msg_(false, "Device lost in vkWaitForFences");
}
@@ -631,7 +555,6 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
// Can't set this until after the fence.
frameData.profilingEnabled_ = enableProfiling;
- frameData.readbackFenceUsed = false;
uint64_t queryResults[MAX_TIMESTAMP_QUERIES];
@@ -671,9 +594,6 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
// Must be after the fence - this performs deletes.
VLOG("PUSH: BeginFrame %d", curFrame);
- if (!run_) {
- WARN_LOG(G3D, "BeginFrame while !run_!");
- }
vulkan_->BeginFrame(enableLogProfiler ? GetInitCmd() : VK_NULL_HANDLE);
@@ -689,31 +609,15 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
frameData.profile.timestampDescriptions.push_back("initCmd Begin");
frameData.profile.timestampDescriptions.push_back("initCmd");
VkCommandBuffer initCmd = GetInitCmd();
- vkCmdResetQueryPool(initCmd, frameData.profile.queryPool, 0, MAX_TIMESTAMP_QUERIES);
- vkCmdWriteTimestamp(initCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, frameData.profile.queryPool, 0);
}
}
VkCommandBuffer VulkanRenderManager::GetInitCmd() {
int curFrame = vulkan_->GetCurFrame();
- FrameData &frameData = frameData_[curFrame];
- if (!frameData.hasInitCommands) {
- VkCommandBufferBeginInfo begin = {
- VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
- nullptr,
- VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
- };
- vkResetCommandPool(vulkan_->GetDevice(), frameData.cmdPoolInit, 0);
- VkResult res = vkBeginCommandBuffer(frameData.initCmd, &begin);
- if (res != VK_SUCCESS) {
- return VK_NULL_HANDLE;
- }
- frameData.hasInitCommands = true;
- }
- return frameData_[curFrame].initCmd;
+ return frameData_[curFrame].GetInitCmd(vulkan_);
}
-VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, uint32_t variantBitmask, const char *tag) {
+VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, const char *tag) {
VKRGraphicsPipeline *pipeline = new VKRGraphicsPipeline();
_dbg_assert_(desc->vertexShader);
_dbg_assert_(desc->fragmentShader);
@@ -739,6 +643,21 @@ VKRGraphicsPipeline *VulkanRenderManager::CreateGraphicsPipeline(VKRGraphicsPipe
if (!(variantBitmask & (1 << i)))
continue;
RenderPassType rpType = (RenderPassType)i;
+
+ // Sanity check - don't compile incompatible types (could be caused by corrupt caches, changes in data structures, etc).
+ if (pipelineFlags & PipelineFlags::USES_DEPTH_STENCIL) {
+ if (!RenderPassTypeHasDepth(rpType)) {
+ WARN_LOG(G3D, "Not compiling pipeline that requires depth, for non depth renderpass type");
+ continue;
+ }
+ }
+ if (pipelineFlags & PipelineFlags::USES_INPUT_ATTACHMENT) {
+ if (!RenderPassTypeHasInput(rpType)) {
+ WARN_LOG(G3D, "Not compiling pipeline that requires input attachment, for non input renderpass type");
+ continue;
+ }
+ }
+
pipeline->pipeline[rpType] = Promise::CreateEmpty();
compileQueue_.push_back(CompileQueueEntry(pipeline, compatibleRenderPass->Get(vulkan_, rpType), rpType));
needsCompile = true;
@@ -768,17 +687,22 @@ void VulkanRenderManager::EndCurRenderStep() {
curRenderStep_->render.colorLoad, curRenderStep_->render.depthLoad, curRenderStep_->render.stencilLoad,
curRenderStep_->render.colorStore, curRenderStep_->render.depthStore, curRenderStep_->render.stencilStore,
};
- RenderPassType rpType = RP_TYPE_COLOR_DEPTH;
+ // Save the accumulated pipeline flags so we can use that to configure the render pass.
+ // We'll often be able to avoid loading/saving the depth/stencil buffer.
curRenderStep_->render.pipelineFlags = curPipelineFlags_;
+ bool depthStencil = (curPipelineFlags_ & PipelineFlags::USES_DEPTH_STENCIL) != 0;
+ RenderPassType rpType = depthStencil ? RP_TYPE_COLOR_DEPTH : RP_TYPE_COLOR;
if (!curRenderStep_->render.framebuffer) {
rpType = RP_TYPE_BACKBUFFER;
+ } else if (curPipelineFlags_ & PipelineFlags::USES_INPUT_ATTACHMENT) {
+ // Not allowed on backbuffers.
+ rpType = depthStencil ? RP_TYPE_COLOR_DEPTH_INPUT : RP_TYPE_COLOR_INPUT;
}
+ // TODO: Also add render pass types for depth/stencil-less.
VKRRenderPass *renderPass = queueRunner_.GetRenderPass(key);
curRenderStep_->render.renderPassType = rpType;
- // Save the accumulated pipeline flags so we can use that to configure the render pass.
- // We'll often be able to avoid loading/saving the depth/stencil buffer.
compileMutex_.lock();
bool needsCompile = false;
for (VKRGraphicsPipeline *pipeline : pipelinesToCheck_) {
@@ -804,7 +728,12 @@ void VulkanRenderManager::EndCurRenderStep() {
// We no longer have a current render step.
curRenderStep_ = nullptr;
- curPipelineFlags_ = 0;
+ 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) {
@@ -817,9 +746,11 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR
}
if (depth == VKRRenderPassLoadAction::CLEAR) {
clearMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
+ curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL;
}
if (stencil == VKRRenderPassLoadAction::CLEAR) {
clearMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
+ curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL;
}
// If we need a clear and the previous step has commands already, it's best to just add a clear and keep going.
@@ -1026,98 +957,6 @@ void VulkanRenderManager::CopyImageToMemorySync(VkImage image, int mipLevel, int
queueRunner_.CopyReadbackBuffer(w, h, destFormat, destFormat, pixelStride, pixels);
}
-bool VulkanRenderManager::InitBackbufferFramebuffers(int width, int height) {
- VkResult res;
- // We share the same depth buffer but have multiple color buffers, see the loop below.
- VkImageView attachments[2] = { VK_NULL_HANDLE, depth_.view };
-
- VkFramebufferCreateInfo fb_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
- fb_info.renderPass = queueRunner_.GetCompatibleRenderPass()->Get(vulkan_, RP_TYPE_BACKBUFFER);
- fb_info.attachmentCount = 2;
- fb_info.pAttachments = attachments;
- fb_info.width = width;
- fb_info.height = height;
- fb_info.layers = 1;
-
- framebuffers_.resize(swapchainImageCount_);
-
- for (uint32_t i = 0; i < swapchainImageCount_; i++) {
- attachments[0] = swapchainImages_[i].view;
- res = vkCreateFramebuffer(vulkan_->GetDevice(), &fb_info, nullptr, &framebuffers_[i]);
- _dbg_assert_(res == VK_SUCCESS);
- if (res != VK_SUCCESS) {
- framebuffers_.clear();
- return false;
- }
- }
-
- return true;
-}
-
-bool VulkanRenderManager::InitDepthStencilBuffer(VkCommandBuffer cmd) {
- const VkFormat depth_format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat;
- int aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
- VkImageCreateInfo image_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
- image_info.imageType = VK_IMAGE_TYPE_2D;
- image_info.format = depth_format;
- image_info.extent.width = vulkan_->GetBackbufferWidth();
- image_info.extent.height = vulkan_->GetBackbufferHeight();
- image_info.extent.depth = 1;
- image_info.mipLevels = 1;
- image_info.arrayLayers = 1;
- image_info.samples = VK_SAMPLE_COUNT_1_BIT;
- image_info.queueFamilyIndexCount = 0;
- image_info.pQueueFamilyIndices = nullptr;
- image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
- image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
- image_info.flags = 0;
-
- depth_.format = depth_format;
-
- VmaAllocationCreateInfo allocCreateInfo{};
- VmaAllocationInfo allocInfo{};
-
- allocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
-
- VkResult res = vmaCreateImage(vulkan_->Allocator(), &image_info, &allocCreateInfo, &depth_.image, &depth_.alloc, &allocInfo);
- _dbg_assert_(res == VK_SUCCESS);
- if (res != VK_SUCCESS)
- return false;
-
- vulkan_->SetDebugName(depth_.image, VK_OBJECT_TYPE_IMAGE, "BackbufferDepth");
-
- TransitionImageLayout2(cmd, depth_.image, 0, 1,
- aspectMask,
- VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
- VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
- VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
- 0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
-
- VkImageViewCreateInfo depth_view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
- depth_view_info.image = depth_.image;
- depth_view_info.format = depth_format;
- depth_view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
- depth_view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
- depth_view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
- depth_view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
- depth_view_info.subresourceRange.aspectMask = aspectMask;
- depth_view_info.subresourceRange.baseMipLevel = 0;
- depth_view_info.subresourceRange.levelCount = 1;
- depth_view_info.subresourceRange.baseArrayLayer = 0;
- depth_view_info.subresourceRange.layerCount = 1;
- depth_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
- depth_view_info.flags = 0;
-
- VkDevice device = vulkan_->GetDevice();
-
- res = vkCreateImageView(device, &depth_view_info, NULL, &depth_.view);
- _dbg_assert_(res == VK_SUCCESS);
- if (res != VK_SUCCESS)
- return false;
-
- return true;
-}
-
static void RemoveDrawCommands(std::vector *cmds) {
// Here we remove any DRAW type commands when we hit a CLEAR.
for (auto &c : *cmds) {
@@ -1192,6 +1031,10 @@ void VulkanRenderManager::Clear(uint32_t clearColor, float clearZ, int clearSten
curRenderStep_->render.depthLoad = (clearMask & VK_IMAGE_ASPECT_DEPTH_BIT) ? VKRRenderPassLoadAction::CLEAR : VKRRenderPassLoadAction::KEEP;
curRenderStep_->render.stencilLoad = (clearMask & VK_IMAGE_ASPECT_STENCIL_BIT) ? VKRRenderPassLoadAction::CLEAR : VKRRenderPassLoadAction::KEEP;
+ if (clearMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
+ curPipelineFlags_ |= PipelineFlags::USES_DEPTH_STENCIL;
+ }
+
// In case there were commands already.
curRenderStep_->render.numDraws = 0;
RemoveDrawCommands(&curRenderStep_->commands);
@@ -1268,7 +1111,6 @@ void VulkanRenderManager::CopyFramebuffer(VKRFramebuffer *src, VkRect2D srcRect,
if (dstPos.x != 0 || dstPos.y != 0 || !fillsDst)
step->dependencies.insert(dst);
- std::unique_lock lock(mutex_);
steps_.push_back(step);
}
@@ -1314,12 +1156,15 @@ void VulkanRenderManager::BlitFramebuffer(VKRFramebuffer *src, VkRect2D srcRect,
if (!fillsDst)
step->dependencies.insert(dst);
- std::unique_lock lock(mutex_);
steps_.push_back(step);
}
VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBit, int attachment) {
_dbg_assert_(curRenderStep_ != nullptr);
+
+ // We don't support texturing from stencil, neither do we support texturing from depth|stencil together (nonsensical).
+ _dbg_assert_(aspectBit == VK_IMAGE_ASPECT_COLOR_BIT || aspectBit == VK_IMAGE_ASPECT_DEPTH_BIT);
+
// Mark the dependency, check for required transitions, and return the image.
// Optimization: If possible, use final*Layout to put the texture into the correct layout "early".
@@ -1346,17 +1191,15 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in
// Track dependencies fully.
curRenderStep_->dependencies.insert(fb);
- if (!curRenderStep_->preTransitions.empty() &&
- curRenderStep_->preTransitions.back().fb == fb &&
- curRenderStep_->preTransitions.back().targetLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
- // We're done.
- return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.imageView : fb->depth.depthSampleView;
- } else {
- curRenderStep_->preTransitions.push_back({ fb, aspectBit, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL });
- return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.imageView : fb->depth.depthSampleView;
- }
+ // Add this pretransition unless we already have it.
+ TransitionRequest rq{ fb, aspectBit, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
+ curRenderStep_->preTransitions.insert(rq); // Note that insert avoids inserting duplicates.
+ return aspectBit == VK_IMAGE_ASPECT_COLOR_BIT ? fb->color.imageView : fb->depth.depthSampleView;
}
+// Called on main thread.
+// Sends the collected commands to the render thread. Submit-latency should be
+// measured from here, probably.
void VulkanRenderManager::Finish() {
EndCurRenderStep();
@@ -1369,22 +1212,20 @@ void VulkanRenderManager::Finish() {
int curFrame = vulkan_->GetCurFrame();
FrameData &frameData = frameData_[curFrame];
- if (!useThread_) {
- frameData.steps = std::move(steps_);
- steps_.clear();
- frameData.type = VKRRunType::END;
- Run(curFrame);
- } else {
- std::unique_lock lock(frameData.pull_mutex);
- VLOG("PUSH: Frame[%d].readyForRun = true", curFrame);
- frameData.steps = std::move(steps_);
- steps_.clear();
- frameData.readyForRun = true;
- frameData.type = VKRRunType::END;
- frameData.pull_condVar.notify_all();
- }
- vulkan_->EndFrame();
+ {
+ VLOG("PUSH: Frame[%d]", curFrame);
+ VKRRenderThreadTask task;
+ task.frame = curFrame;
+ task.runType = VKRRunType::PRESENT;
+ std::unique_lock lock(pushMutex_);
+ renderThreadQueue_.push(task);
+ renderThreadQueue_.back().steps = std::move(steps_);
+ pushCondVar_.notify_one();
+ }
+
+ steps_.clear();
+ vulkan_->EndFrame();
insideFrame_ = false;
}
@@ -1395,223 +1236,105 @@ void VulkanRenderManager::Wipe() {
steps_.clear();
}
-// Can be called multiple times with no bad side effects. This is so that we can either begin a frame the normal way,
-// or stop it in the middle for a synchronous readback, then start over again mostly normally but without repeating
-// the backbuffer image acquisition.
-void VulkanRenderManager::BeginSubmitFrame(int frame) {
- FrameData &frameData = frameData_[frame];
- if (!frameData.hasBegun) {
- // Get the index of the next available swapchain image, and a semaphore to block command buffer execution on.
- VkResult res = vkAcquireNextImageKHR(vulkan_->GetDevice(), vulkan_->GetSwapchain(), UINT64_MAX, acquireSemaphore_, (VkFence)VK_NULL_HANDLE, &frameData.curSwapchainImage);
- if (res == VK_SUBOPTIMAL_KHR) {
- // Hopefully the resize will happen shortly. Ignore - one frame might look bad or something.
- WARN_LOG(G3D, "VK_SUBOPTIMAL_KHR returned - ignoring");
- } else if (res == VK_ERROR_OUT_OF_DATE_KHR) {
- WARN_LOG(G3D, "VK_ERROR_OUT_OF_DATE_KHR returned - processing the frame, but not presenting");
- frameData.skipSwap = true;
- } else {
- _assert_msg_(res == VK_SUCCESS, "vkAcquireNextImageKHR failed! result=%s", VulkanResultToString(res));
- }
+// Called on the render thread.
+//
+// Can be called again after a VKRRunType::SYNC on the same frame.
+void VulkanRenderManager::Run(VKRRenderThreadTask &task) {
+ FrameData &frameData = frameData_[task.frame];
+ _dbg_assert_(!frameData.hasPresentCommands);
+ frameData.SubmitPending(vulkan_, FrameSubmitType::Pending, frameDataShared_);
+
+ if (!frameData.hasMainCommands) {
+ // Effectively resets both main and present command buffers, since they both live in this pool.
+ // We always record main commands first, so we don't need to reset the present command buffer separately.
vkResetCommandPool(vulkan_->GetDevice(), frameData.cmdPoolMain, 0);
+
VkCommandBufferBeginInfo begin{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
- res = vkBeginCommandBuffer(frameData.mainCmd, &begin);
-
+ VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin);
+ frameData.hasMainCommands = true;
_assert_msg_(res == VK_SUCCESS, "vkBeginCommandBuffer failed! result=%s", VulkanResultToString(res));
-
- queueRunner_.SetBackbuffer(framebuffers_[frameData.curSwapchainImage], swapchainImages_[frameData.curSwapchainImage].image);
-
- frameData.hasBegun = true;
- }
-}
-
-void VulkanRenderManager::Submit(int frame, bool triggerFrameFence) {
- FrameData &frameData = frameData_[frame];
- if (frameData.hasInitCommands) {
- if (frameData.profilingEnabled_ && triggerFrameFence) {
- // Pre-allocated query ID 1.
- vkCmdWriteTimestamp(frameData.initCmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, frameData.profile.queryPool, 1);
- }
- VkResult res = vkEndCommandBuffer(frameData.initCmd);
- _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (init)! result=%s", VulkanResultToString(res));
}
- VkResult res = vkEndCommandBuffer(frameData.mainCmd);
- _assert_msg_(res == VK_SUCCESS, "vkEndCommandBuffer failed (main)! result=%s", VulkanResultToString(res));
-
- VkCommandBuffer cmdBufs[2];
- int numCmdBufs = 0;
- if (frameData.hasInitCommands) {
- cmdBufs[numCmdBufs++] = frameData.initCmd;
- if (splitSubmit_) {
- // Send the init commands off separately. Used this once to confirm that the cause of a device loss was in the init cmdbuf.
- VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO };
- submit_info.commandBufferCount = (uint32_t)numCmdBufs;
- submit_info.pCommandBuffers = cmdBufs;
- res = vkQueueSubmit(vulkan_->GetGraphicsQueue(), 1, &submit_info, VK_NULL_HANDLE);
- if (res == VK_ERROR_DEVICE_LOST) {
- _assert_msg_(false, "Lost the Vulkan device in split submit! If this happens again, switch Graphics Backend away from Vulkan");
- } else {
- _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (init)! result=%s", VulkanResultToString(res));
- }
- numCmdBufs = 0;
- }
- }
- cmdBufs[numCmdBufs++] = frameData.mainCmd;
-
- VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO };
- VkPipelineStageFlags waitStage[1]{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
- if (triggerFrameFence && !frameData.skipSwap) {
- submit_info.waitSemaphoreCount = 1;
- submit_info.pWaitSemaphores = &acquireSemaphore_;
- submit_info.pWaitDstStageMask = waitStage;
- }
- submit_info.commandBufferCount = (uint32_t)numCmdBufs;
- submit_info.pCommandBuffers = cmdBufs;
- if (triggerFrameFence && !frameData.skipSwap) {
- submit_info.signalSemaphoreCount = 1;
- submit_info.pSignalSemaphores = &renderingCompleteSemaphore_;
- }
- res = vkQueueSubmit(vulkan_->GetGraphicsQueue(), 1, &submit_info, triggerFrameFence ? frameData.fence : frameData.readbackFence);
- if (res == VK_ERROR_DEVICE_LOST) {
- _assert_msg_(false, "Lost the Vulkan device in vkQueueSubmit! If this happens again, switch Graphics Backend away from Vulkan");
- } else {
- _assert_msg_(res == VK_SUCCESS, "vkQueueSubmit failed (main, split=%d)! result=%s", (int)splitSubmit_, VulkanResultToString(res));
- }
-
- // When !triggerFence, we notify after syncing with Vulkan.
- if (useThread_ && triggerFrameFence) {
- VLOG("PULL: Frame %d.readyForFence = true", frame);
- std::unique_lock lock(frameData.push_mutex);
- frameData.readyForFence = true;
- frameData.push_condVar.notify_all();
- }
-
- frameData.hasInitCommands = false;
-}
-
-void VulkanRenderManager::EndSubmitFrame(int frame) {
- FrameData &frameData = frameData_[frame];
- frameData.hasBegun = false;
-
- Submit(frame, true);
-
- if (!frameData.skipSwap) {
- VkSwapchainKHR swapchain = vulkan_->GetSwapchain();
- VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
- present.swapchainCount = 1;
- present.pSwapchains = &swapchain;
- present.pImageIndices = &frameData.curSwapchainImage;
- present.pWaitSemaphores = &renderingCompleteSemaphore_;
- present.waitSemaphoreCount = 1;
-
- VkResult res = vkQueuePresentKHR(vulkan_->GetGraphicsQueue(), &present);
- if (res == VK_ERROR_OUT_OF_DATE_KHR) {
- // We clearly didn't get this in vkAcquireNextImageKHR because of the skipSwap check above.
- // Do the increment.
- outOfDateFrames_++;
- } else if (res == VK_SUBOPTIMAL_KHR) {
- outOfDateFrames_++;
- } else if (res != VK_SUCCESS) {
- _assert_msg_(false, "vkQueuePresentKHR failed! result=%s", VulkanResultToString(res));
- } else {
- // Success
- outOfDateFrames_ = 0;
- }
- } else {
- // We only get here if vkAcquireNextImage returned VK_ERROR_OUT_OF_DATE.
- outOfDateFrames_++;
- frameData.skipSwap = false;
- }
-}
-
-void VulkanRenderManager::Run(int frame) {
- BeginSubmitFrame(frame);
-
- FrameData &frameData = frameData_[frame];
- auto &stepsOnThread = frameData_[frame].steps;
- VkCommandBuffer cmd = frameData.mainCmd;
- queueRunner_.PreprocessSteps(stepsOnThread);
+ queueRunner_.PreprocessSteps(task.steps);
+ // Likely during shutdown, happens in headless.
+ if (task.steps.empty() && !frameData.hasAcquired)
+ frameData.skipSwap = true;
//queueRunner_.LogSteps(stepsOnThread, false);
- queueRunner_.RunSteps(cmd, stepsOnThread, frameData.profilingEnabled_ ? &frameData.profile : nullptr);
- stepsOnThread.clear();
+ queueRunner_.RunSteps(task.steps, frameData, frameDataShared_);
- switch (frameData.type) {
- case VKRRunType::END:
- EndSubmitFrame(frame);
+ switch (task.runType) {
+ case VKRRunType::PRESENT:
+ frameData.SubmitPending(vulkan_, FrameSubmitType::Present, frameDataShared_);
+
+ if (!frameData.skipSwap) {
+ VkResult res = frameData.QueuePresent(vulkan_, frameDataShared_);
+ if (res == VK_ERROR_OUT_OF_DATE_KHR) {
+ // We clearly didn't get this in vkAcquireNextImageKHR because of the skipSwap check above.
+ // Do the increment.
+ outOfDateFrames_++;
+ } else if (res == VK_SUBOPTIMAL_KHR) {
+ outOfDateFrames_++;
+ } else if (res != VK_SUCCESS) {
+ _assert_msg_(false, "vkQueuePresentKHR failed! result=%s", VulkanResultToString(res));
+ } else {
+ // Success
+ outOfDateFrames_ = 0;
+ }
+ } else {
+ // We only get here if vkAcquireNextImage returned VK_ERROR_OUT_OF_DATE.
+ outOfDateFrames_++;
+ frameData.skipSwap = false;
+ }
break;
case VKRRunType::SYNC:
- EndSyncFrame(frame);
+ // The submit will trigger the readbackFence, and also do the wait for it.
+ frameData.SubmitPending(vulkan_, FrameSubmitType::Sync, frameDataShared_);
+
+ {
+ std::unique_lock lock(syncMutex_);
+ syncCondVar_.notify_one();
+ }
+
+ // At this point the GPU is idle, and we can resume filling the command buffers for the
+ // current frame since and thus all previously enqueued command buffers have been
+ // processed. No need to switch to the next frame number, would just be confusing.
break;
default:
_dbg_assert_(false);
}
- VLOG("PULL: Finished running frame %d", frame);
-}
-
-void VulkanRenderManager::EndSyncFrame(int frame) {
- FrameData &frameData = frameData_[frame];
-
- frameData.readbackFenceUsed = true;
-
- // The submit will trigger the readbackFence.
- Submit(frame, false);
-
- // Hard stall of the GPU, not ideal, but necessary so the CPU has the contents of the readback.
- vkWaitForFences(vulkan_->GetDevice(), 1, &frameData.readbackFence, true, UINT64_MAX);
- vkResetFences(vulkan_->GetDevice(), 1, &frameData.readbackFence);
-
- // At this point we can resume filling the command buffers for the current frame since
- // we know the device is idle - and thus all previously enqueued command buffers have been processed.
- // No need to switch to the next frame number.
- VkCommandBufferBeginInfo begin{
- VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
- nullptr,
- VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
- };
- vkResetCommandPool(vulkan_->GetDevice(), frameData.cmdPoolMain, 0);
- VkResult res = vkBeginCommandBuffer(frameData.mainCmd, &begin);
- _assert_(res == VK_SUCCESS);
-
- if (useThread_) {
- std::unique_lock lock(frameData.push_mutex);
- frameData.readyForFence = true;
- frameData.push_condVar.notify_all();
- }
+ VLOG("PULL: Finished running frame %d", task.frame);
}
+// Called from main thread.
void VulkanRenderManager::FlushSync() {
renderStepOffset_ += (int)steps_.size();
int curFrame = vulkan_->GetCurFrame();
FrameData &frameData = frameData_[curFrame];
- if (!useThread_) {
- frameData.steps = std::move(steps_);
- steps_.clear();
- frameData.type = VKRRunType::SYNC;
- Run(curFrame);
- } else {
- std::unique_lock lock(frameData.pull_mutex);
- VLOG("PUSH: Frame[%d].readyForRun = true (sync)", curFrame);
- frameData.steps = std::move(steps_);
- steps_.clear();
- frameData.readyForRun = true;
- _dbg_assert_(!frameData.readyForFence);
- frameData.type = VKRRunType::SYNC;
- frameData.pull_condVar.notify_all();
+
+ {
+ VLOG("PUSH: Frame[%d]", curFrame);
+ VKRRenderThreadTask task;
+ task.frame = curFrame;
+ task.runType = VKRRunType::SYNC;
+ std::unique_lock lock(pushMutex_);
+ renderThreadQueue_.push(task);
+ renderThreadQueue_.back().steps = std::move(steps_);
+ pushCondVar_.notify_one();
}
- if (useThread_) {
- std::unique_lock lock(frameData.push_mutex);
+ {
+ std::unique_lock lock(syncMutex_);
// Wait for the flush to be hit, since we're syncing.
- while (!frameData.readyForFence) {
+ while (!frameData.syncDone) {
VLOG("PUSH: Waiting for frame[%d].readyForFence = 1 (sync)", curFrame);
- frameData.push_condVar.wait(lock);
+ syncCondVar_.wait(lock);
}
- frameData.readyForFence = false;
+ frameData.syncDone = false;
}
}
diff --git a/Common/GPU/Vulkan/VulkanRenderManager.h b/Common/GPU/Vulkan/VulkanRenderManager.h
index 4cc7aafdfc..7fd45e2246 100644
--- a/Common/GPU/Vulkan/VulkanRenderManager.h
+++ b/Common/GPU/Vulkan/VulkanRenderManager.h
@@ -43,20 +43,22 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int
class VKRFramebuffer {
public:
- VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, const char *tag);
+ VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, bool createDepthStencilBuffer, const char *tag);
~VKRFramebuffer();
VkFramebuffer Get(VKRRenderPass *compatibleRenderPass, RenderPassType rpType);
int width = 0;
int height = 0;
- VKRImage color{};
- VKRImage depth{};
+ VKRImage color{}; // color.image is always there.
+ VKRImage depth{}; // depth.image is allowed to be VK_NULL_HANDLE.
const char *Tag() const {
return tag_.c_str();
}
+ void UpdateTag(const char *newTag);
+
// TODO: Hide.
VulkanContext *vulkan_;
private:
@@ -65,15 +67,6 @@ private:
std::string tag_;
};
-enum class VKRRunType {
- END,
- SYNC,
-};
-
-enum {
- MAX_TIMESTAMP_QUERIES = 128,
-};
-
struct BoundingRect {
int x1;
int y1;
@@ -135,6 +128,7 @@ struct VKRGraphicsPipelineDesc {
// Replaced the ShaderStageInfo with promises here so we can wait for compiles to finish.
Promise *vertexShader = nullptr;
Promise *fragmentShader = nullptr;
+ Promise *geometryShader = nullptr;
VkPipelineInputAssemblyStateCreateInfo inputAssembly{ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
VkVertexInputAttributeDescription attrs[8]{};
@@ -157,6 +151,12 @@ struct VKRComputePipelineDesc {
// Wrapped pipeline. Doesn't own desc.
struct VKRGraphicsPipeline {
+ ~VKRGraphicsPipeline() {
+ for (int i = 0; i < RP_TYPE_COUNT; i++) {
+ delete pipeline[i];
+ }
+ }
+
bool Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType);
// This deletes the whole VKRGraphicsPipeline, you must remove your last pointer to it when doing this.
@@ -170,11 +170,12 @@ struct VKRGraphicsPipeline {
};
struct VKRComputePipeline {
- VKRComputePipeline() {
- pipeline = VK_NULL_HANDLE;
+ ~VKRComputePipeline() {
+ delete pipeline;
}
+
VKRComputePipelineDesc *desc = nullptr;
- Promise *pipeline;
+ Promise *pipeline = nullptr;
bool Create(VulkanContext *vulkan);
bool Pending() const {
@@ -202,16 +203,10 @@ public:
VulkanRenderManager(VulkanContext *vulkan);
~VulkanRenderManager();
- void ThreadFunc();
- void CompileThreadFunc();
- void DrainCompileQueue();
-
// Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again.
void BeginFrame(bool enableProfiling, bool enableLogProfiler);
// Can run on a different thread!
void Finish();
- void Run(int frame);
-
// Zaps queued up commands. Use if you know there's a risk you've queued up stuff that has already been deleted. Can happen during in-game shutdown.
void Wipe();
@@ -236,6 +231,8 @@ public:
// as the other backends, even though there's no actual binding happening here.
VkImageView BindFramebufferAsTexture(VKRFramebuffer *fb, int binding, VkImageAspectFlags aspectBits, int attachment);
+ void BindCurrentFramebufferAsInputAttachment0(VkImageAspectFlags aspectBits);
+
bool CopyFramebufferToMemorySync(VKRFramebuffer *src, VkImageAspectFlags aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, 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);
@@ -247,7 +244,7 @@ public:
// We delay creating pipelines until the end of the current render pass, so we can create the right type immediately.
// Unless a variantBitmask is passed in, in which case we can just go ahead.
// WARNING: desc must stick around during the lifetime of the pipeline! It's not enough to build it on the stack and drop it.
- VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, uint32_t variantBitmask, const char *tag);
+ VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc, PipelineFlags pipelineFlags, uint32_t variantBitmask, const char *tag);
VKRComputePipeline *CreateComputePipeline(VKRComputePipelineDesc *desc);
void NudgeCompilerThread() {
@@ -440,11 +437,7 @@ public:
void DestroyBackbuffers();
bool HasBackbuffers() {
- return !framebuffers_.empty();
- }
-
- void SetSplitSubmit(bool split) {
- splitSubmit_ = split;
+ return queueRunner_.HasBackbuffers();
}
void SetInflightFrames(int f) {
@@ -470,57 +463,20 @@ public:
}
private:
- bool InitBackbufferFramebuffers(int width, int height);
- bool InitDepthStencilBuffer(VkCommandBuffer cmd); // Used for non-buffered rendering.
void EndCurRenderStep();
+ void ThreadFunc();
+ void CompileThreadFunc();
+ void DrainCompileQueue();
+
+ void Run(VKRRenderThreadTask &task);
void BeginSubmitFrame(int frame);
- void EndSubmitFrame(int frame);
- void Submit(int frame, bool triggerFence);
// Bad for performance but sometimes necessary for synchronous CPU readbacks (screenshots and whatnot).
void FlushSync();
- void EndSyncFrame(int frame);
-
void StopThread();
- // Permanent objects
- VkSemaphore acquireSemaphore_;
- VkSemaphore renderingCompleteSemaphore_;
-
- // Per-frame data, round-robin so we can overlap submission with execution of the previous frame.
- struct FrameData {
- std::mutex push_mutex;
- std::condition_variable push_condVar;
-
- std::mutex pull_mutex;
- std::condition_variable pull_condVar;
-
- bool readyForFence = true;
- bool readyForRun = false;
- bool skipSwap = false;
- VKRRunType type = VKRRunType::END;
-
- VkFence fence;
- VkFence readbackFence; // Strictly speaking we might only need one of these.
- bool readbackFenceUsed = false;
-
- // These are on different threads so need separate pools.
- VkCommandPool cmdPoolInit;
- VkCommandPool cmdPoolMain;
- VkCommandBuffer initCmd;
- VkCommandBuffer mainCmd;
- bool hasInitCommands = false;
- std::vector steps;
-
- // Swapchain.
- bool hasBegun = false;
- uint32_t curSwapchainImage = -1;
-
- // Profiling.
- QueueProfileContext profile;
- bool profilingEnabled_;
- };
+ FrameDataShared frameDataShared_;
FrameData frameData_[VulkanContext::MAX_INFLIGHT_FRAMES];
int newInflightFrames_ = -1;
@@ -539,25 +495,33 @@ private:
int curHeight_ = -1;
bool insideFrame_ = false;
+ bool run_ = false;
+
// This is the offset within this frame, in case of a mid-frame sync.
int renderStepOffset_ = 0;
VKRStep *curRenderStep_ = nullptr;
bool curStepHasViewport_ = false;
bool curStepHasScissor_ = false;
- u32 curPipelineFlags_ = 0;
+ PipelineFlags curPipelineFlags_{};
BoundingRect curRenderArea_;
std::vector steps_;
- bool splitSubmit_ = false;
// Execution time state
- bool run_ = true;
VulkanContext *vulkan_;
std::thread thread_;
- std::mutex mutex_;
- int threadInitFrame_ = 0;
VulkanQueueRunner queueRunner_;
+ // For pushing data on the queue.
+ std::mutex pushMutex_;
+ std::condition_variable pushCondVar_;
+
+ std::queue renderThreadQueue_;
+
+ // For readbacks and other reasons we need to sync with the render thread.
+ std::mutex syncMutex_;
+ std::condition_variable syncCondVar_;
+
// Shader compilation thread to compile while emulating the rest of the frame.
// Only one right now but we could use more.
std::thread compileThread_;
@@ -568,23 +532,4 @@ private:
// pipelines to check and possibly create at the end of the current render pass.
std::vector pipelinesToCheck_;
-
- // Swap chain management
- struct SwapchainImageData {
- VkImage image;
- VkImageView view;
- };
- std::vector framebuffers_;
- std::vector swapchainImages_;
- uint32_t swapchainImageCount_ = 0;
- struct DepthBufferInfo {
- VkFormat format = VK_FORMAT_UNDEFINED;
- VkImage image = VK_NULL_HANDLE;
- VmaAllocation alloc = VK_NULL_HANDLE;
- VkImageView view = VK_NULL_HANDLE;
- };
- DepthBufferInfo depth_;
-
- // This works great - except see issue #10097. WTF?
- bool useThread_ = true;
};
diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp
index f673691224..cf2a28ea53 100644
--- a/Common/GPU/Vulkan/thin3d_vulkan.cpp
+++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp
@@ -177,9 +177,9 @@ VkShaderStageFlagBits StageToVulkan(ShaderStage stage) {
case ShaderStage::Vertex: return VK_SHADER_STAGE_VERTEX_BIT;
case ShaderStage::Geometry: return VK_SHADER_STAGE_GEOMETRY_BIT;
case ShaderStage::Compute: return VK_SHADER_STAGE_COMPUTE_BIT;
- default:
case ShaderStage::Fragment: return VK_SHADER_STAGE_FRAGMENT_BIT;
}
+ return VK_SHADER_STAGE_FRAGMENT_BIT;
}
// Not registering this as a resource holder, instead the pipeline is registered. It will
@@ -194,17 +194,19 @@ public:
~VKShaderModule() {
if (module_) {
DEBUG_LOG(G3D, "Queueing %s (shmodule %p) for release", tag_.c_str(), module_);
- vulkan_->Delete().QueueDeleteShaderModule(module_);
+ VkShaderModule shaderModule = module_->BlockUntilReady();
+ vulkan_->Delete().QueueDeleteShaderModule(shaderModule);
+ delete module_;
}
}
- VkShaderModule Get() const { return module_; }
+ Promise *Get() const { return module_; }
ShaderStage GetStage() const override {
return stage_;
}
private:
VulkanContext *vulkan_;
- VkShaderModule module_ = VK_NULL_HANDLE;
+ Promise *module_ = nullptr;
VkShaderStageFlagBits vkstage_;
bool ok_ = false;
ShaderStage stage_;
@@ -213,8 +215,8 @@ private:
};
bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, const uint8_t *data, size_t size) {
- vulkan_ = vulkan;
// We'll need this to free it later.
+ vulkan_ = vulkan;
source_ = (const char *)data;
std::vector spirv;
std::string errorMessage;
@@ -232,7 +234,9 @@ bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, con
}
#endif
- if (vulkan->CreateShaderModule(spirv, &module_, vkstage_ == VK_SHADER_STAGE_VERTEX_BIT ? "thin3d_vs" : "thin3d_fs")) {
+ VkShaderModule shaderModule = VK_NULL_HANDLE;
+ if (vulkan->CreateShaderModule(spirv, &shaderModule, vkstage_ == VK_SHADER_STAGE_VERTEX_BIT ? "thin3d_vs" : "thin3d_fs")) {
+ module_ = Promise::AlreadyDone(shaderModule);
ok_ = true;
} else {
WARN_LOG(G3D, "vkCreateShaderModule failed (%s)", tag_.c_str());
@@ -274,7 +278,6 @@ public:
return buf->PushAligned(ubo_, uboSize_, vulkan->GetPhysicalDeviceProperties().properties.limits.minUniformBufferOffsetAlignment, vkbuf);
}
- int GetUniformLoc(const char *name);
int GetUBOSize() const {
return uboSize_;
}
@@ -361,7 +364,7 @@ class VKFramebuffer;
class VKContext : public DrawContext {
public:
- VKContext(VulkanContext *vulkan, bool splitSubmit);
+ VKContext(VulkanContext *vulkan);
virtual ~VKContext();
const DeviceCaps &GetDeviceCaps() const override {
@@ -401,9 +404,10 @@ public:
// These functions should be self explanatory.
void BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPassInfo &rp, const char *tag) override;
Framebuffer *GetCurrentRenderTarget() override {
- return curFramebuffer_;
+ return (Framebuffer *)curFramebuffer_.ptr;
}
void BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChannel channelBit, int attachment) override;
+ void BindCurrentFramebufferForColorInput() override;
void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override;
@@ -414,6 +418,7 @@ public:
void BindSamplerStates(int start, int count, SamplerState **state) override;
void BindTextures(int start, int count, Texture **textures) override;
+ void BindNativeTexture(int sampler, void *nativeTexture) override;
void BindPipeline(Pipeline *pipeline) override {
curPipeline_ = (VKPipeline *)pipeline;
@@ -472,27 +477,7 @@ public:
std::vector GetFeatureList() const override;
std::vector GetExtensionList() const override;
- uint64_t GetNativeObject(NativeObject obj, void *srcObject) override {
- switch (obj) {
- case NativeObject::CONTEXT:
- return (uint64_t)vulkan_;
- case NativeObject::INIT_COMMANDBUFFER:
- return (uint64_t)renderManager_.GetInitCmd();
- case NativeObject::BOUND_TEXTURE0_IMAGEVIEW:
- return (uint64_t)boundImageView_[0];
- case NativeObject::BOUND_TEXTURE1_IMAGEVIEW:
- return (uint64_t)boundImageView_[1];
- case NativeObject::RENDER_MANAGER:
- return (uint64_t)(uintptr_t)&renderManager_;
- case NativeObject::NULL_IMAGEVIEW:
- return (uint64_t)GetNullTexture()->GetImageView();
- case NativeObject::TEXTURE_VIEW:
- return (uint64_t)(((VKTexture *)srcObject)->GetImageView());
- default:
- Crash();
- return 0;
- }
- }
+ uint64_t GetNativeObject(NativeObject obj, void *srcObject) override;
void HandleEvent(Event ev, int width, int height, void *param1, void *param2) override;
@@ -521,7 +506,7 @@ private:
VkDescriptorSetLayout descriptorSetLayout_ = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
VkPipelineCache pipelineCache_ = VK_NULL_HANDLE;
- AutoRef curFramebuffer_;
+ AutoRef curFramebuffer_;
VkDevice device_;
VkQueue queue_;
@@ -563,6 +548,11 @@ static int GetBpp(VkFormat format) {
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_B8G8R8A8_UNORM:
return 32;
+ case VK_FORMAT_R8_UNORM:
+ return 8;
+ case VK_FORMAT_R8G8_UNORM:
+ case VK_FORMAT_R16_UNORM:
+ return 16;
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
@@ -586,6 +576,9 @@ static VkFormat DataFormatToVulkan(DataFormat format) {
case DataFormat::D32F: return VK_FORMAT_D32_SFLOAT;
case DataFormat::D32F_S8: return VK_FORMAT_D32_SFLOAT_S8_UINT;
case DataFormat::S8: return VK_FORMAT_S8_UINT;
+
+ case DataFormat::R16_UNORM: return VK_FORMAT_R16_UNORM;
+
case DataFormat::R16_FLOAT: return VK_FORMAT_R16_SFLOAT;
case DataFormat::R16G16_FLOAT: return VK_FORMAT_R16G16_SFLOAT;
case DataFormat::R16G16B16A16_FLOAT: return VK_FORMAT_R16G16B16A16_SFLOAT;
@@ -772,7 +765,7 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const Textur
return true;
}
-VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit)
+VKContext::VKContext(VulkanContext *vulkan)
: vulkan_(vulkan), renderManager_(vulkan) {
shaderLanguageDesc_.Init(GLSL_VULKAN);
@@ -798,9 +791,11 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit)
caps_.fragmentShaderInt32Supported = true;
caps_.textureNPOTFullySupported = true;
caps_.fragmentShaderDepthWriteSupported = true;
+ caps_.blendMinMaxSupported = true;
caps_.logicOpSupported = vulkan->GetDeviceFeatures().enabled.logicOp != 0;
auto deviceProps = vulkan->GetPhysicalDeviceProperties(vulkan_->GetCurrentPhysicalDeviceIndex()).properties;
+
switch (deviceProps.vendorID) {
case VULKAN_VENDOR_AMD: caps_.vendor = GPUVendor::VENDOR_AMD; break;
case VULKAN_VENDOR_ARM: caps_.vendor = GPUVendor::VENDOR_ARM; break;
@@ -822,6 +817,11 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit)
// Color write mask not masking write in certain scenarios with a depth test, see #10421.
// Known still present on driver 0x80180000 and Adreno 5xx (possibly more.)
bugs_.Infest(Bugs::COLORWRITEMASK_BROKEN_WITH_DEPTHTEST);
+
+ // Trying to follow all the rules in https://registry.khronos.org/vulkan/specs/1.3/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
+ // and https://registry.khronos.org/vulkan/specs/1.3/html/vkspec.html#renderpass-feedbackloop, but still it doesn't
+ // quite work - artifacts on triangle boundaries on Adreno.
+ bugs_.Infest(Bugs::SUBPASS_FEEDBACK_BROKEN);
} else if (caps_.vendor == GPUVendor::VENDOR_AMD) {
// See issue #10074, and also #10065 (AMD) and #10109 for the choice of the driver version to check for.
if (deviceProps.driverVersion < 0x00407000) {
@@ -831,19 +831,32 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit)
// Workaround for Intel driver bug. TODO: Re-enable after some driver version
bugs_.Infest(Bugs::DUAL_SOURCE_BLENDING_BROKEN);
} else if (caps_.vendor == GPUVendor::VENDOR_ARM) {
+ int majorVersion = VK_API_VERSION_MAJOR(deviceProps.driverVersion);
+
// These GPUs (up to some certain hardware version?) have a bug where draws where gl_Position.w == .z
// corrupt the depth buffer. This is easily worked around by simply scaling Z down a tiny bit when this case
// is detected. See: https://github.com/hrydgard/ppsspp/issues/11937
bugs_.Infest(Bugs::EQUAL_WZ_CORRUPTS_DEPTH);
- // At least one driver at the upper end of the range is known to be likely to suffer from the bug causing issue #13833 (Midnight Club map broken).
- bugs_.Infest(Bugs::MALI_STENCIL_DISCARD_BUG);
- // This started in driver 31 or 32.
- if (VK_API_VERSION_MAJOR(deviceProps.driverVersion) >= 32) {
+ // Nearly identical to the the Adreno bug, see #13833 (Midnight Club map broken) and other issues.
+ // Reported fixed in major version 40 - let's add a check once confirmed.
+ bugs_.Infest(Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL);
+
+ // This started in driver 31 or 32, fixed in 40 - let's add a check once confirmed.
+ if (majorVersion >= 32) {
bugs_.Infest(Bugs::MALI_CONSTANT_LOAD_BUG); // See issue #15661
}
+
+ // Older ARM devices have very slow geometry shaders, not worth using. At least before 15.
+ if (majorVersion <= 15) {
+ bugs_.Infest(Bugs::GEOMETRY_SHADERS_SLOW);
+ }
}
+ // Limited, through input attachments and self-dependencies.
+ // We turn it off here already if buggy.
+ caps_.framebufferFetchSupported = !bugs_.Has(Bugs::SUBPASS_FEEDBACK_BROKEN);
+
caps_.deviceID = deviceProps.deviceID;
device_ = vulkan->GetDevice();
@@ -911,8 +924,6 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit)
VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_);
_assert_(VK_SUCCESS == res);
-
- renderManager_.SetSplitSubmit(splitSubmit);
}
VKContext::~VKContext() {
@@ -973,7 +984,7 @@ void VKContext::WipeQueue() {
}
VkDescriptorSet VKContext::GetOrCreateDescriptorSet(VkBuffer buf) {
- DescriptorSetKey key;
+ DescriptorSetKey key{};
FrameData *frame = &frame_[vulkan_->GetCurFrame()];
@@ -1049,12 +1060,13 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
VKDepthStencilState *depth = (VKDepthStencilState *)desc.depthStencil;
VKRasterState *raster = (VKRasterState *)desc.raster;
- u32 pipelineFlags = 0;
+ PipelineFlags pipelineFlags = (PipelineFlags)0;
if (depth->info.depthTestEnable || depth->info.stencilTestEnable) {
- pipelineFlags |= PIPELINE_FLAG_USES_DEPTH_STENCIL;
+ pipelineFlags |= PipelineFlags::USES_DEPTH_STENCIL;
}
+ // TODO: We need code to set USES_BLEND_CONSTANT here too, if we're ever gonna use those in thin3d code.
- VKPipeline *pipeline = new VKPipeline(vulkan_, desc.uniformDesc ? desc.uniformDesc->uniformBufferSize : 16 * sizeof(float), (PipelineFlags)pipelineFlags, tag);
+ VKPipeline *pipeline = new VKPipeline(vulkan_, desc.uniformDesc ? desc.uniformDesc->uniformBufferSize : 16 * sizeof(float), pipelineFlags, tag);
VKRGraphicsPipelineDesc &gDesc = pipeline->vkrDesc;
@@ -1066,11 +1078,12 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
vkshader->AddRef();
pipeline->deps.push_back(vkshader);
if (vkshader->GetStage() == ShaderStage::Vertex) {
- gDesc.vertexShader = Promise::AlreadyDone(vkshader->Get());
+ gDesc.vertexShader = vkshader->Get();
} else if (vkshader->GetStage() == ShaderStage::Fragment) {
- gDesc.fragmentShader = Promise::AlreadyDone(vkshader->Get());
+ gDesc.fragmentShader = vkshader->Get();
} else {
ERROR_LOG(G3D, "Bad stage");
+ delete pipeline;
return nullptr;
}
}
@@ -1126,7 +1139,7 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char
VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
raster->ToVulkan(&gDesc.rs);
- pipeline->pipeline = renderManager_.CreateGraphicsPipeline(&gDesc, 1 << RP_TYPE_BACKBUFFER, tag ? tag : "thin3d");
+ pipeline->pipeline = renderManager_.CreateGraphicsPipeline(&gDesc, pipelineFlags, 1 << RP_TYPE_BACKBUFFER, tag ? tag : "thin3d");
if (desc.uniformDesc) {
pipeline->dynamicUniformSize = (int)desc.uniformDesc->uniformBufferSize;
@@ -1284,6 +1297,11 @@ void VKContext::BindTextures(int start, int count, Texture **textures) {
}
}
+void VKContext::BindNativeTexture(int sampler, void *nativeTexture) {
+ boundTextures_[sampler] = nullptr;
+ boundImageView_[sampler] = (VkImageView)nativeTexture;
+}
+
ShaderModule *VKContext::CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t size, const char *tag) {
VKShaderModule *shader = new VKShaderModule(stage, tag);
if (shader->Compile(vulkan_, language, data, size)) {
@@ -1295,17 +1313,6 @@ ShaderModule *VKContext::CreateShaderModule(ShaderStage stage, ShaderLanguage la
}
}
-int VKPipeline::GetUniformLoc(const char *name) {
- int loc = -1;
-
- // HACK! As we only use one uniform we hardcode it.
- if (!strcmp(name, "WorldViewProj")) {
- return 0;
- }
-
- return loc;
-}
-
void VKContext::UpdateDynamicUniformBuffer(const void *ub, size_t size) {
curPipeline_->SetDynamicUniformData(ub, size);
}
@@ -1387,8 +1394,8 @@ void VKContext::Clear(int clearMask, uint32_t colorval, float depthVal, int sten
renderManager_.Clear(colorval, depthVal, stencilVal, mask);
}
-DrawContext *T3DCreateVulkanContext(VulkanContext *vulkan, bool split) {
- return new VKContext(vulkan, split);
+DrawContext *T3DCreateVulkanContext(VulkanContext *vulkan) {
+ return new VKContext(vulkan);
}
void AddFeature(std::vector &features, const char *name, VkBool32 available, VkBool32 enabled) {
@@ -1418,7 +1425,7 @@ std::vector VKContext::GetFeatureList() const {
AddFeature(features, "occlusionQueryPrecise", available.occlusionQueryPrecise, enabled.occlusionQueryPrecise);
AddFeature(features, "multiDrawIndirect", available.multiDrawIndirect, enabled.multiDrawIndirect);
- features.push_back(std::string("Preferred depth buffer format: ") + VulkanFormatToString(vulkan_->GetDeviceInfo().preferredDepthStencilFormat));
+ features.emplace_back(std::string("Preferred depth buffer format: ") + VulkanFormatToString(vulkan_->GetDeviceInfo().preferredDepthStencilFormat));
return features;
}
@@ -1477,15 +1484,16 @@ public:
buf_ = nullptr;
}
VKRFramebuffer *GetFB() const { return buf_; }
+ void UpdateTag(const char *newTag) override {
+ buf_->UpdateTag(newTag);
+ }
private:
VKRFramebuffer *buf_;
};
Framebuffer *VKContext::CreateFramebuffer(const FramebufferDesc &desc) {
VkCommandBuffer cmd = renderManager_.GetInitCmd();
- // TODO: We always create with depth here, even when it's not needed (such as color temp FBOs).
- // Should optimize those away.
- VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetQueueRunner()->GetCompatibleRenderPass(), desc.width, desc.height, desc.tag);
+ VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetQueueRunner()->GetCompatibleRenderPass(), desc.width, desc.height, desc.z_stencil, desc.tag);
return new VKFramebuffer(vkrfb);
}
@@ -1570,6 +1578,10 @@ void VKContext::BindFramebufferAsTexture(Framebuffer *fbo, int binding, FBChanne
boundImageView_[binding] = renderManager_.BindFramebufferAsTexture(fb->GetFB(), binding, aspect, attachment);
}
+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) {
@@ -1610,4 +1622,28 @@ void VKContext::InvalidateFramebuffer(FBInvalidationStage stage, uint32_t channe
}
}
+uint64_t VKContext::GetNativeObject(NativeObject obj, void *srcObject) {
+ switch (obj) {
+ case NativeObject::CONTEXT:
+ return (uint64_t)vulkan_;
+ case NativeObject::INIT_COMMANDBUFFER:
+ return (uint64_t)renderManager_.GetInitCmd();
+ case NativeObject::BOUND_TEXTURE0_IMAGEVIEW:
+ return (uint64_t)boundImageView_[0];
+ case NativeObject::BOUND_TEXTURE1_IMAGEVIEW:
+ return (uint64_t)boundImageView_[1];
+ case NativeObject::RENDER_MANAGER:
+ return (uint64_t)(uintptr_t)&renderManager_;
+ case NativeObject::NULL_IMAGEVIEW:
+ return (uint64_t)GetNullTexture()->GetImageView();
+ case NativeObject::TEXTURE_VIEW:
+ return (uint64_t)(((VKTexture *)srcObject)->GetImageView());
+ case NativeObject::BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW:
+ return (uint64_t)curFramebuffer_->GetFB()->color.imageView;
+ default:
+ Crash();
+ return 0;
+ }
+}
+
} // namespace Draw
diff --git a/Common/GPU/thin3d.cpp b/Common/GPU/thin3d.cpp
index d712195c7b..a81c38ba10 100644
--- a/Common/GPU/thin3d.cpp
+++ b/Common/GPU/thin3d.cpp
@@ -33,6 +33,9 @@ size_t DataFormatSizeInBytes(DataFormat fmt) {
case DataFormat::R8G8B8A8_SNORM: return 4;
case DataFormat::R8G8B8A8_UINT: return 4;
case DataFormat::R8G8B8A8_SINT: return 4;
+
+ case DataFormat::R16_UNORM: return 2;
+
case DataFormat::R16_FLOAT: return 2;
case DataFormat::R16G16_FLOAT: return 4;
case DataFormat::R16G16B16A16_FLOAT: return 8;
@@ -432,7 +435,7 @@ vec3 hsv2rgb(vec3 c) {
}
layout (location = 0) in vec4 pos;
layout (location = 1) in vec4 inColor;
-layout (location = 2) in vec2 inTexCoord;
+layout (location = 3) in vec2 inTexCoord;
layout (location = 0) out vec4 outColor;
layout (location = 1) out vec2 outTexCoord;
out gl_PerVertex { vec4 gl_Position; };
@@ -447,6 +450,8 @@ void main() {
)"
} };
+static_assert(SEM_TEXCOORD0 == 3, "Semantic shader hardcoded in glsl above.");
+
const UniformBufferDesc vsTexColBufDesc{ sizeof(VsTexColUB),{
{ "WorldViewProj", 0, -1, UniformType::MATRIX4X4, 0 },
{ "TintSaturation", 4, -1, UniformType::FLOAT2, 64 },
@@ -675,9 +680,9 @@ const char *Bugs::GetBugName(uint32_t bug) {
case COLORWRITEMASK_BROKEN_WITH_DEPTHTEST: return "COLORWRITEMASK_BROKEN_WITH_DEPTHTEST";
case BROKEN_FLAT_IN_SHADER: return "BROKEN_FLAT_IN_SHADER";
case EQUAL_WZ_CORRUPTS_DEPTH: return "EQUAL_WZ_CORRUPTS_DEPTH";
- case MALI_STENCIL_DISCARD_BUG: return "MALI_STENCIL_DISCARD_BUG";
case RASPBERRY_SHADER_COMP_HANG: return "RASPBERRY_SHADER_COMP_HANG";
case MALI_CONSTANT_LOAD_BUG: return "MALI_CONSTANT_LOAD_BUG";
+ case SUBPASS_FEEDBACK_BROKEN: return "SUBPASS_FEEDBACK_BROKEN";
default: return "(N/A)";
}
}
diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h
index 6196f494f9..c04b7119cd 100644
--- a/Common/GPU/thin3d.h
+++ b/Common/GPU/thin3d.h
@@ -114,6 +114,7 @@ enum BufferUsageFlag : int {
enum Semantic : int {
SEM_POSITION,
SEM_COLOR0,
+ SEM_COLOR1,
SEM_TEXCOORD0,
SEM_TEXCOORD1,
SEM_NORMAL,
@@ -242,6 +243,7 @@ enum class NativeObject {
INIT_COMMANDBUFFER,
BOUND_TEXTURE0_IMAGEVIEW,
BOUND_TEXTURE1_IMAGEVIEW,
+ BOUND_FRAMEBUFFER_COLOR_IMAGEVIEW,
RENDER_MANAGER,
TEXTURE_VIEW,
NULL_IMAGEVIEW,
@@ -328,14 +330,17 @@ public:
COLORWRITEMASK_BROKEN_WITH_DEPTHTEST = 5,
BROKEN_FLAT_IN_SHADER = 6,
EQUAL_WZ_CORRUPTS_DEPTH = 7,
- MALI_STENCIL_DISCARD_BUG = 8,
- RASPBERRY_SHADER_COMP_HANG = 9,
- MALI_CONSTANT_LOAD_BUG = 10,
+ RASPBERRY_SHADER_COMP_HANG = 8,
+ MALI_CONSTANT_LOAD_BUG = 9,
+ SUBPASS_FEEDBACK_BROKEN = 10,
+ GEOMETRY_SHADERS_SLOW = 11,
MAX_BUG,
};
protected:
uint32_t flags_ = 0;
+
+ static_assert(sizeof(flags_) * 8 > MAX_BUG, "Ran out of space for bugs.");
};
class RefCountedObject {
@@ -410,6 +415,7 @@ class Framebuffer : public RefCountedObject {
public:
int Width() { return width_; }
int Height() { return height_; }
+ virtual void UpdateTag(const char *tag) {}
protected:
int width_ = -1, height_ = -1;
};
@@ -546,6 +552,7 @@ struct DeviceCaps {
bool textureNPOTFullySupported;
bool fragmentShaderDepthWriteSupported;
bool textureDepthSupported;
+ bool blendMinMaxSupported;
std::string deviceName; // The device name to use when creating the thin3d context, to get the same one.
};
@@ -557,6 +564,7 @@ typedef std::function adapterNames);
#endif
-DrawContext *T3DCreateVulkanContext(VulkanContext *context, bool splitSubmit);
+DrawContext *T3DCreateVulkanContext(VulkanContext *context);
} // namespace Draw
diff --git a/Common/LogReporting.cpp b/Common/LogReporting.cpp
index 2189b1c7e2..0fcc78d0ad 100644
--- a/Common/LogReporting.cpp
+++ b/Common/LogReporting.cpp
@@ -35,7 +35,7 @@ bool ShouldLogNTimes(const char *identifier, int count) {
std::lock_guard lock(logNTimesLock);
auto iter = logNTimes.find(identifier);
if (iter == logNTimes.end()) {
- logNTimes.insert(std::pair(identifier, 1));
+ logNTimes.emplace(identifier, 1);
return true;
} else {
if (iter->second >= count) {
diff --git a/Common/RiscVEmitter.cpp b/Common/RiscVEmitter.cpp
index 2fc0966c1f..3f0d47499b 100644
--- a/Common/RiscVEmitter.cpp
+++ b/Common/RiscVEmitter.cpp
@@ -628,7 +628,7 @@ void RiscVEmitter::FlushIcache() {
void RiscVEmitter::FlushIcacheSection(const u8 *start, const u8 *end) {
#if PPSSPP_ARCH(RISCV64)
- __builtin___clear_cache(start, end);
+ __builtin___clear_cache((void *)start, (void *)end);
#endif
}
diff --git a/Common/StringUtils.cpp b/Common/StringUtils.cpp
index f88f6194ab..3a9e29c61a 100644
--- a/Common/StringUtils.cpp
+++ b/Common/StringUtils.cpp
@@ -273,7 +273,7 @@ void SplitString(const std::string& str, const char delim, std::vector& output)
if (str[pos] == '\"' || str[pos] == '\'') {
if (even) {
//quoted text
- output.push_back(str.substr(next, pos - next));
+ output.emplace_back(str.substr(next, pos - next));
even = 0;
} else {
//non quoted text
diff --git a/Common/Thread/Promise.h b/Common/Thread/Promise.h
index 6fae6d7a2b..4145b4b6c1 100644
--- a/Common/Thread/Promise.h
+++ b/Common/Thread/Promise.h
@@ -79,7 +79,6 @@ public:
// Returns T if the data is ready, nullptr if it's not.
T Poll() {
- _dbg_assert_(this != nullptr);
std::lock_guard guard(readyMutex_);
if (ready_) {
return data_;
@@ -96,7 +95,6 @@ public:
}
T BlockUntilReady() {
- _dbg_assert_(this != nullptr);
std::lock_guard guard(readyMutex_);
if (ready_) {
return data_;
diff --git a/Common/UI/Context.cpp b/Common/UI/Context.cpp
index e9fe8ed0c5..21c687bd2b 100644
--- a/Common/UI/Context.cpp
+++ b/Common/UI/Context.cpp
@@ -170,7 +170,7 @@ void UIContext::ActivateTopScissor() {
int h = std::max(0.0f, ceilf(scale_y * bounds.h));
if (x < 0 || y < 0 || x + w > pixel_xres || y + h > pixel_yres) {
// This won't actually report outside a game, but we can try.
- ERROR_LOG_REPORT(G3D, "UI scissor out of bounds: %d,%d-%d,%d / %d,%d", x, y, w, h, pixel_xres, pixel_yres);
+ ERROR_LOG_REPORT(G3D, "UI scissor out of bounds in %sScreen: %d,%d-%d,%d / %d,%d", screenTag_ ? screenTag_ : "N/A", x, y, w, h, pixel_xres, pixel_yres);
x = std::max(0, x);
y = std::max(0, y);
w = std::min(w, pixel_xres - x);
diff --git a/Common/UI/Context.h b/Common/UI/Context.h
index ad02b4027c..ac26ab0202 100644
--- a/Common/UI/Context.h
+++ b/Common/UI/Context.h
@@ -74,7 +74,6 @@ public:
const UI::Theme *theme;
// Utility methods
-
TextDrawer *Text() const { return textDrawer_; }
void SetFontStyle(const UI::FontStyle &style);
@@ -103,6 +102,10 @@ public:
void setUIAtlas(const std::string &name);
+ void SetScreenTag(const char *tag) {
+ screenTag_ = tag;
+ }
+
private:
Draw::DrawContext *draw_ = nullptr;
Bounds bounds_;
@@ -126,4 +129,6 @@ private:
std::string lastUIAtlas_;
std::string UIAtlas_ = "ui_atlas.zim";
+
+ const char *screenTag_ = nullptr;
};
diff --git a/Common/UI/Screen.h b/Common/UI/Screen.h
index de24c74985..42f0830627 100644
--- a/Common/UI/Screen.h
+++ b/Common/UI/Screen.h
@@ -71,7 +71,7 @@ public:
// what screen it is.
virtual void *dialogData() { return 0; }
- virtual std::string tag() const { return std::string(""); }
+ virtual const char *tag() const = 0;
virtual bool isTransparent() const { return false; }
virtual bool isTopLevel() const { return false; }
diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp
index 8026de37bc..ecebae3a81 100644
--- a/Common/UI/UIScreen.cpp
+++ b/Common/UI/UIScreen.cpp
@@ -117,6 +117,9 @@ void UIScreen::render() {
if (root_) {
UIContext *uiContext = screenManager()->getUIContext();
+
+ uiContext->SetScreenTag(tag());
+
UI::LayoutViewHierarchy(*uiContext, root_, ignoreInsets_);
uiContext->PushTransform({translation_, scale_, alpha_});
diff --git a/Common/UI/UIScreen.h b/Common/UI/UIScreen.h
index 43ec130d9d..4b113d3001 100644
--- a/Common/UI/UIScreen.h
+++ b/Common/UI/UIScreen.h
@@ -136,7 +136,7 @@ public:
void SetHiddenChoices(std::set hidden) {
hidden_ = hidden;
}
- virtual std::string tag() const override { return std::string("listpopup"); }
+ const char *tag() const override { return "listpopup"; }
UI::Event OnChoice;
@@ -187,6 +187,8 @@ public:
disabled_ = *value_ < 0;
}
+ const char *tag() const override { return "SliderPopup"; }
+
Event OnChange;
private:
@@ -214,6 +216,8 @@ public:
: PopupScreen(title, "OK", "Cancel"), units_(units), value_(value), originalValue_(*value), minValue_(minValue), maxValue_(maxValue), step_(step), changing_(false), liveUpdate_(liveUpdate) {}
void CreatePopupContents(UI::ViewGroup *parent) override;
+ const char *tag() const override { return "SliderFloatPopup"; }
+
Event OnChange;
private:
@@ -241,6 +245,8 @@ public:
: PopupScreen(title, "OK", "Cancel"), value_(value), placeholder_(placeholder), maxLen_(maxLen) {}
virtual void CreatePopupContents(ViewGroup *parent) override;
+ const char *tag() const override { return "TextEditPopup"; }
+
Event OnChange;
private:
diff --git a/Common/VR/PPSSPPVR.cpp b/Common/VR/PPSSPPVR.cpp
index a18bfa11c3..18b9f00e3c 100644
--- a/Common/VR/PPSSPPVR.cpp
+++ b/Common/VR/PPSSPPVR.cpp
@@ -1,4 +1,6 @@
+#include "Common/GPU/OpenGL/GLRenderManager.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
+
#include "Common/VR/PPSSPPVR.h"
#include "Common/VR/VRBase.h"
#include "Common/VR/VRInput.h"
@@ -11,6 +13,7 @@
#include "Core/KeyMap.h"
#include "Core/System.h"
+static long vrCompat[VR_COMPAT_MAX];
/*
================================================================================
@@ -229,6 +232,59 @@ void UpdateVRScreenKey(const KeyInput &key) {
/*
================================================================================
+// VR games compatibility
+
+================================================================================
+*/
+
+void PreprocessSkyplane(GLRStep* step) {
+
+ // Do not do anything if the scene is not in VR.
+ if (IsFlatVRScene()) {
+ return;
+ }
+
+ // Check if it is the step we need to modify.
+ for (auto& cmd : step->commands) {
+ if (cmd.cmd == GLRRenderCommand::BIND_FB_TEXTURE) {
+ return;
+ }
+ }
+
+ // Clear sky with the fog color.
+ if (!vrCompat[VR_COMPAT_FBO_CLEAR]) {
+ GLRRenderData skyClear {};
+ skyClear.cmd = GLRRenderCommand::CLEAR;
+ skyClear.clear.colorMask = 0xF;
+ skyClear.clear.clearMask = GL_COLOR_BUFFER_BIT;
+ skyClear.clear.clearColor = vrCompat[VR_COMPAT_FOG_COLOR];
+ step->commands.insert(step->commands.begin(), skyClear);
+ vrCompat[VR_COMPAT_FBO_CLEAR] = true;
+ }
+
+ // Remove original sky plane.
+ bool depthEnabled = false;
+ for (auto& command : step->commands) {
+ if (command.cmd == GLRRenderCommand::DEPTH) {
+ depthEnabled = command.depth.enabled;
+ } else if ((command.cmd == GLRRenderCommand::DRAW_INDEXED) && !depthEnabled) {
+ command.drawIndexed.count = 0;
+ }
+ }
+}
+
+void PreprocessStepVR(void* step) {
+ auto* glrStep = (GLRStep*)step;
+ if (vrCompat[VR_COMPAT_SKYPLANE]) PreprocessSkyplane(glrStep);
+}
+
+void SetVRCompat(VRCompatFlag flag, long value) {
+ vrCompat[flag] = value;
+}
+
+/*
+================================================================================
+
VR rendering integration
================================================================================
@@ -248,12 +304,16 @@ bool StartVRRender() {
// Decide if the scene is 3D or not
if (g_Config.bEnableVR && !VR_GetConfig(VR_CONFIG_FORCE_2D) && (VR_GetConfig(VR_CONFIG_3D_GEOMETRY_COUNT) > 15)) {
- VR_SetConfig(VR_CONFIG_MODE, g_Config.bEnableStereo ? VR_MODE_STEREO_6DOF : VR_MODE_MONO_6DOF);
+ bool stereo = VR_GetConfig(VR_CONFIG_6DOF_PRECISE) && g_Config.bEnableStereo;
+ VR_SetConfig(VR_CONFIG_MODE, stereo ? VR_MODE_STEREO_6DOF : VR_MODE_MONO_6DOF);
} else {
VR_SetConfig(VR_CONFIG_MODE, VR_MODE_FLAT_SCREEN);
}
VR_SetConfig(VR_CONFIG_3D_GEOMETRY_COUNT, VR_GetConfig(VR_CONFIG_3D_GEOMETRY_COUNT) / 2);
+ // Set compatibility
+ vrCompat[VR_COMPAT_SKYPLANE] = PSP_CoreParameter().compat.vrCompat().Skyplane;
+
// Set customizations
VR_SetConfig(VR_CONFIG_6DOF_ENABLED, g_Config.bEnable6DoF);
VR_SetConfig(VR_CONFIG_CANVAS_DISTANCE, g_Config.iCanvasDistance);
@@ -269,6 +329,7 @@ void FinishVRRender() {
void PreVRFrameRender(int fboIndex) {
VR_BeginFrame(VR_GetEngine(), fboIndex);
+ vrCompat[VR_COMPAT_FBO_CLEAR] = false;
}
void PostVRFrameRender() {
diff --git a/Common/VR/PPSSPPVR.h b/Common/VR/PPSSPPVR.h
index e5191c1e46..6fbe2f00fb 100644
--- a/Common/VR/PPSSPPVR.h
+++ b/Common/VR/PPSSPPVR.h
@@ -3,6 +3,20 @@
#include "Common/Input/InputState.h"
#include "Common/Input/KeyCodes.h"
+enum VRCompatFlag {
+ //compatibility tweaks
+ VR_COMPAT_SKYPLANE,
+
+ //render state
+ VR_COMPAT_FBO_CLEAR,
+
+ //uniforms
+ VR_COMPAT_FOG_COLOR,
+
+ //end
+ VR_COMPAT_MAX
+};
+
#ifdef OPENXR
// VR app flow integration
@@ -13,6 +27,10 @@ void GetVRResolutionPerEye(int* width, int* height);
void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(const TouchInput &touch), bool haptics, float dp_xscale, float dp_yscale);
void UpdateVRScreenKey(const KeyInput &key);
+// VR games compatibility
+void PreprocessStepVR(void* step);
+void SetVRCompat(VRCompatFlag flag, long value);
+
// VR rendering integration
void BindVRFramebuffer();
bool StartVRRender();
@@ -36,6 +54,10 @@ inline void GetVRResolutionPerEye(int* width, int* height) {}
inline void UpdateVRInput(bool(*NativeKey)(const KeyInput &key), bool(*NativeTouch)(const TouchInput &touch), bool haptics, float dp_xscale, float dp_yscale) {}
inline void UpdateVRScreenKey(const KeyInput &key) {}
+// VR games compatibility
+inline void PreprocessStepVR(void* step) {}
+inline void SetVRCompat(VRCompatFlag flag, long value) {}
+
// VR rendering integration
inline void BindVRFramebuffer() {}
inline bool StartVRRender() { return false; }
diff --git a/Common/VR/VRBase.cpp b/Common/VR/VRBase.cpp
index e146967b42..abbad01d78 100644
--- a/Common/VR/VRBase.cpp
+++ b/Common/VR/VRBase.cpp
@@ -6,6 +6,36 @@
#include
#include
+#ifdef OPENXR_PLATFORM_PICO
+enum ConfigsSetEXT {
+ UNREAL_VERSION = 0,
+ TRACKING_ORIGIN,
+ OPENGL_NOERROR,
+ ENABLE_SIX_DOF,
+ PRESENTATION_FLAG,
+ ENABLE_CPT,
+ PLATFORM,
+ FOVEATION_LEVEL,
+ SET_DISPLAY_RATE = 8,
+ MRC_TEXTURE_ID = 9,
+};
+
+enum PxrTrackingDof {
+ PXR_TRACKING_3DOF = 0,
+ PXR_TRACKING_6DOF = 1
+};
+
+typedef XrResult (XRAPI_PTR *PFN_xrSetEngineVersionPico)(XrInstance instance,const char* version);
+typedef XrResult (XRAPI_PTR *PFN_xrStartCVControllerThreadPico)(XrInstance instance,int headSensorState, int handSensorState);
+typedef XrResult (XRAPI_PTR *PFN_xrStopCVControllerThreadPico)(XrInstance instance,int headSensorState, int handSensorState);
+typedef XrResult (XRAPI_PTR *PFN_xrSetConfigPICO) (XrSession instance, enum ConfigsSetEXT configIndex, char* configData);
+
+PFN_xrSetConfigPICO pfnXrSetConfigPICO = nullptr;
+PFN_xrSetEngineVersionPico pfnXrSetEngineVersionPico = nullptr;
+PFN_xrStartCVControllerThreadPico pfnXrStartCVControllerThreadPico = nullptr;
+PFN_xrStopCVControllerThreadPico pfnXrStopCVControllerThreadPico = nullptr;
+#endif
+
static engine_t vr_engine;
int vr_initialized = 0;
@@ -39,6 +69,14 @@ void VR_Init( ovrJava java, bool useVulkan ) {
extensions.push_back(XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME);
extensions.push_back(XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME);
#endif
+#ifdef OPENXR_PLATFORM_PICO
+ extensions.push_back(XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME);
+ extensions.push_back("XR_PICO_android_controller_function_ext_enable");
+ extensions.push_back("XR_PICO_view_state_ext_enable");
+ extensions.push_back("XR_PICO_frame_end_info_ext");
+ extensions.push_back("XR_PICO_configs_ext");
+ extensions.push_back("XR_PICO_reset_sensor");
+#endif
// Create the OpenXR instance.
XrApplicationInfo appInfo;
@@ -52,7 +90,14 @@ void VR_Init( ovrJava java, bool useVulkan ) {
XrInstanceCreateInfo instanceCreateInfo;
memset(&instanceCreateInfo, 0, sizeof(instanceCreateInfo));
instanceCreateInfo.type = XR_TYPE_INSTANCE_CREATE_INFO;
+#ifdef OPENXR_PLATFORM_PICO
+ XrInstanceCreateInfoAndroidKHR instanceCreateInfoAndroid = {XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR};
+ instanceCreateInfoAndroid.applicationVM = java.Vm;
+ instanceCreateInfoAndroid.applicationActivity = java.ActivityObject;
+ instanceCreateInfo.next = (XrBaseInStructure*)&instanceCreateInfoAndroid;
+#else
instanceCreateInfo.next = NULL;
+#endif
instanceCreateInfo.createFlags = 0;
instanceCreateInfo.applicationInfo = appInfo;
instanceCreateInfo.enabledApiLayerCount = 0;
@@ -67,6 +112,15 @@ void VR_Init( ovrJava java, bool useVulkan ) {
exit(1);
}
+#ifdef OPENXR_PLATFORM_PICO
+ xrGetInstanceProcAddr(vr_engine.appState.Instance, "xrSetEngineVersionPico", (PFN_xrVoidFunction*)(&pfnXrSetEngineVersionPico));
+ xrGetInstanceProcAddr(vr_engine.appState.Instance, "xrStartCVControllerThreadPico", (PFN_xrVoidFunction*)(&pfnXrStartCVControllerThreadPico));
+ xrGetInstanceProcAddr(vr_engine.appState.Instance, "xrStopCVControllerThreadPico", (PFN_xrVoidFunction*)(&pfnXrStopCVControllerThreadPico));
+ xrGetInstanceProcAddr(vr_engine.appState.Instance,"xrSetConfigPICO", (PFN_xrVoidFunction*)(&pfnXrSetConfigPICO));
+ if (pfnXrSetEngineVersionPico != nullptr) pfnXrSetEngineVersionPico(vr_engine.appState.Instance, "2.8.0.1");
+ if (pfnXrStartCVControllerThreadPico != nullptr) pfnXrStartCVControllerThreadPico(vr_engine.appState.Instance, PXR_TRACKING_6DOF, PXR_TRACKING_6DOF);
+#endif
+
XrInstanceProperties instanceInfo;
instanceInfo.type = XR_TYPE_INSTANCE_PROPERTIES;
instanceInfo.next = NULL;
@@ -124,6 +178,11 @@ void VR_Init( ovrJava java, bool useVulkan ) {
void VR_Destroy( engine_t* engine ) {
if (engine == &vr_engine) {
+#ifdef OPENXR_PLATFORM_PICO
+ if (pfnXrStopCVControllerThreadPico != nullptr) {
+ pfnXrStopCVControllerThreadPico(engine->appState.Instance, PXR_TRACKING_6DOF, PXR_TRACKING_6DOF);
+ }
+#endif
xrDestroyInstance(engine->appState.Instance);
ovrApp_Destroy(&engine->appState);
}
@@ -160,6 +219,9 @@ void VR_EnterVR( engine_t* engine, XrGraphicsBindingVulkanKHR* graphicsBindingVu
ALOGE("Failed to create XR session: %d.", initResult);
exit(1);
}
+#ifdef OPENXR_PLATFORM_PICO
+ pfnXrSetConfigPICO(engine->appState.Session, TRACKING_ORIGIN, "1");
+#endif
// Create a space to the first path
XrReferenceSpaceCreateInfo spaceCreateInfo = {};
diff --git a/Common/VR/VRBase.h b/Common/VR/VRBase.h
index 89bec2b3dd..c05e4739a9 100644
--- a/Common/VR/VRBase.h
+++ b/Common/VR/VRBase.h
@@ -82,7 +82,9 @@ static void OXR_CheckErrors(XrInstance instance, XrResult result, const char* fu
#define OXR(func) func;
#endif
+#ifdef OPENXR_PLATFORM_QUEST
#define OPENXR_HAS_PERFORMANCE_EXTENSION
+#endif
enum { ovrMaxLayerCount = 1 };
enum { ovrMaxNumEyes = 2 };
diff --git a/Common/VR/VRFramebuffer.cpp b/Common/VR/VRFramebuffer.cpp
index 4a71cb5cd8..1a62417c3e 100644
--- a/Common/VR/VRFramebuffer.cpp
+++ b/Common/VR/VRFramebuffer.cpp
@@ -75,7 +75,7 @@ bool ovrFramebuffer_CreateGL(XrSession session, ovrFramebuffer* frameBuffer, int
frameBuffer->DepthSwapChain.Height = swapChainCreateInfo.height;
// Create the color swapchain.
- swapChainCreateInfo.format = GL_RGBA8;
+ swapChainCreateInfo.format = GL_SRGB8_ALPHA8;
swapChainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
OXR(xrCreateSwapchain(session, &swapChainCreateInfo, &frameBuffer->ColorSwapChain.Handle));
OXR(xrEnumerateSwapchainImages(frameBuffer->ColorSwapChain.Handle, 0, &frameBuffer->TextureSwapChainLength, NULL));
diff --git a/Common/VR/VRInput.cpp b/Common/VR/VRInput.cpp
index 63fc97a030..2fc8816f61 100644
--- a/Common/VR/VRInput.cpp
+++ b/Common/VR/VRInput.cpp
@@ -26,7 +26,6 @@ XrActionSet runningActionSet;
XrSpace leftControllerAimSpace = XR_NULL_HANDLE;
XrSpace rightControllerAimSpace = XR_NULL_HANDLE;
int inputInitialized = 0;
-int useSimpleProfile = 0;
int in_vrEventTime = 0;
double lastframetime = 0;
@@ -248,182 +247,109 @@ void IN_VRInit( engine_t *engine ) {
handPoseRightAction = CreateAction(runningActionSet, XR_ACTION_TYPE_POSE_INPUT, "hand_pose_right", NULL, 1, &rightHandPath);
XrPath interactionProfilePath = XR_NULL_PATH;
- XrPath interactionProfilePathTouch = XR_NULL_PATH;
- XrPath interactionProfilePathKHRSimple = XR_NULL_PATH;
+#ifdef OPENXR_PLATFORM_QUEST
+ OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/oculus/touch_controller", &interactionProfilePath));
+#elif OPENXR_PLATFORM_PICO
+ OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/pico/neo3_controller", &interactionProfilePath));
+#endif
+ // Map bindings
+ XrActionSuggestedBinding bindings[32]; // large enough for all profiles
+ int currBinding = 0;
- OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/oculus/touch_controller", &interactionProfilePathTouch));
- OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/khr/simple_controller", &interactionProfilePathKHRSimple));
+#ifdef OPENXR_PLATFORM_QUEST
+ bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger");
+ bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger");
+ bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/menu/click");
+#elif OPENXR_PLATFORM_PICO
+ bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger/click");
+ bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger/click");
+ bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/back/click");
+ bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/right/input/back/click");
+#endif
+ bindings[currBinding++] = ActionSuggestedBinding(buttonXAction, "/user/hand/left/input/x/click");
+ bindings[currBinding++] = ActionSuggestedBinding(buttonYAction, "/user/hand/left/input/y/click");
+ bindings[currBinding++] = ActionSuggestedBinding(buttonAAction, "/user/hand/right/input/a/click");
+ bindings[currBinding++] = ActionSuggestedBinding(buttonBAction, "/user/hand/right/input/b/click");
+ bindings[currBinding++] = ActionSuggestedBinding(gripLeftAction, "/user/hand/left/input/squeeze/value");
+ bindings[currBinding++] = ActionSuggestedBinding(gripRightAction, "/user/hand/right/input/squeeze/value");
+ bindings[currBinding++] = ActionSuggestedBinding(moveOnLeftJoystickAction, "/user/hand/left/input/thumbstick");
+ bindings[currBinding++] = ActionSuggestedBinding(moveOnRightJoystickAction, "/user/hand/right/input/thumbstick");
+ bindings[currBinding++] = ActionSuggestedBinding(thumbstickLeftClickAction, "/user/hand/left/input/thumbstick/click");
+ bindings[currBinding++] = ActionSuggestedBinding(thumbstickRightClickAction, "/user/hand/right/input/thumbstick/click");
+ bindings[currBinding++] = ActionSuggestedBinding(vibrateLeftFeedback, "/user/hand/left/output/haptic");
+ bindings[currBinding++] = ActionSuggestedBinding(vibrateRightFeedback, "/user/hand/right/output/haptic");
+ bindings[currBinding++] = ActionSuggestedBinding(handPoseLeftAction, "/user/hand/left/input/aim/pose");
+ bindings[currBinding++] = ActionSuggestedBinding(handPoseRightAction, "/user/hand/right/input/aim/pose");
- // Toggle this to force simple as a first choice, otherwise use it as a last resort
- if (useSimpleProfile) {
- ALOGV("xrSuggestInteractionProfileBindings found bindings for Khronos SIMPLE controller");
- interactionProfilePath = interactionProfilePathKHRSimple;
- } else {
- // Query Set
- XrActionSet queryActionSet = CreateActionSet(1, "query_action_set", "Action Set used to query device caps");
- XrAction dummyAction = CreateAction(queryActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "dummy_action", "Dummy Action", 0, NULL);
+ XrInteractionProfileSuggestedBinding suggestedBindings = {};
+ suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
+ suggestedBindings.next = NULL;
+ suggestedBindings.interactionProfile = interactionProfilePath;
+ suggestedBindings.suggestedBindings = bindings;
+ suggestedBindings.countSuggestedBindings = currBinding;
+ OXR(xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings));
- // Map bindings
- XrActionSuggestedBinding bindings[1];
- int currBinding = 0;
- bindings[currBinding++] = ActionSuggestedBinding(dummyAction, "/user/hand/right/input/system/click");
+ // Attach actions
+ XrSessionActionSetsAttachInfo attachInfo = {};
+ attachInfo.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO;
+ attachInfo.next = NULL;
+ attachInfo.countActionSets = 1;
+ attachInfo.actionSets = &runningActionSet;
+ OXR(xrAttachSessionActionSets(engine->appState.Session, &attachInfo));
- XrInteractionProfileSuggestedBinding suggestedBindings = {};
- suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
- suggestedBindings.next = NULL;
- suggestedBindings.suggestedBindings = bindings;
- suggestedBindings.countSuggestedBindings = currBinding;
+ // Enumerate actions
+ XrPath actionPathsBuffer[32];
+ char stringBuffer[256];
+ XrAction actionsToEnumerate[] = {
+ indexLeftAction,
+ indexRightAction,
+ menuAction,
+ buttonAAction,
+ buttonBAction,
+ buttonXAction,
+ buttonYAction,
+ gripLeftAction,
+ gripRightAction,
+ moveOnLeftJoystickAction,
+ moveOnRightJoystickAction,
+ thumbstickLeftClickAction,
+ thumbstickRightClickAction,
+ vibrateLeftFeedback,
+ vibrateRightFeedback,
+ handPoseLeftAction,
+ handPoseRightAction
+ };
+ for (XrAction & i : actionsToEnumerate) {
+ XrBoundSourcesForActionEnumerateInfo enumerateInfo = {};
+ enumerateInfo.type = XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO;
+ enumerateInfo.next = NULL;
+ enumerateInfo.action = i;
- // Try all
- suggestedBindings.interactionProfile = interactionProfilePathTouch;
- XrResult suggestTouchResult = xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings);
- OXR(suggestTouchResult);
+ // Get Count
+ uint32_t countOutput = 0;
+ OXR(xrEnumerateBoundSourcesForAction(engine->appState.Session, &enumerateInfo, 0 /* request size */, &countOutput, NULL));
+ ALOGV("xrEnumerateBoundSourcesForAction action=%lld count=%u", (long long)enumerateInfo.action, countOutput);
- if (XR_SUCCESS == suggestTouchResult) {
- ALOGV("xrSuggestInteractionProfileBindings found bindings for QUEST controller");
- interactionProfilePath = interactionProfilePathTouch;
- }
-
- if (interactionProfilePath == XR_NULL_PATH) {
- // Simple as a fallback
- bindings[0] = ActionSuggestedBinding(dummyAction, "/user/hand/right/input/select/click");
- suggestedBindings.interactionProfile = interactionProfilePathKHRSimple;
- XrResult suggestKHRSimpleResult = xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings);
- OXR(suggestKHRSimpleResult);
- if (XR_SUCCESS == suggestKHRSimpleResult) {
- ALOGV("xrSuggestInteractionProfileBindings found bindings for Khronos SIMPLE controller");
- interactionProfilePath = interactionProfilePathKHRSimple;
- } else {
- ALOGE("xrSuggestInteractionProfileBindings did NOT find any bindings.");
- exit(1);
- }
- }
- }
-
- // Action creation
- {
- // Map bindings
- XrActionSuggestedBinding bindings[32]; // large enough for all profiles
- int currBinding = 0;
-
- {
- if (interactionProfilePath == interactionProfilePathTouch) {
- bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger");
- bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger");
- bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/menu/click");
- bindings[currBinding++] = ActionSuggestedBinding(buttonXAction, "/user/hand/left/input/x/click");
- bindings[currBinding++] = ActionSuggestedBinding(buttonYAction, "/user/hand/left/input/y/click");
- bindings[currBinding++] = ActionSuggestedBinding(buttonAAction, "/user/hand/right/input/a/click");
- bindings[currBinding++] = ActionSuggestedBinding(buttonBAction, "/user/hand/right/input/b/click");
- bindings[currBinding++] = ActionSuggestedBinding(gripLeftAction, "/user/hand/left/input/squeeze/value");
- bindings[currBinding++] = ActionSuggestedBinding(gripRightAction, "/user/hand/right/input/squeeze/value");
- bindings[currBinding++] = ActionSuggestedBinding(moveOnLeftJoystickAction, "/user/hand/left/input/thumbstick");
- bindings[currBinding++] = ActionSuggestedBinding(moveOnRightJoystickAction, "/user/hand/right/input/thumbstick");
- bindings[currBinding++] = ActionSuggestedBinding(thumbstickLeftClickAction, "/user/hand/left/input/thumbstick/click");
- bindings[currBinding++] = ActionSuggestedBinding(thumbstickRightClickAction, "/user/hand/right/input/thumbstick/click");
- bindings[currBinding++] = ActionSuggestedBinding(vibrateLeftFeedback, "/user/hand/left/output/haptic");
- bindings[currBinding++] = ActionSuggestedBinding(vibrateRightFeedback, "/user/hand/right/output/haptic");
- bindings[currBinding++] = ActionSuggestedBinding(handPoseLeftAction, "/user/hand/left/input/aim/pose");
- bindings[currBinding++] = ActionSuggestedBinding(handPoseRightAction, "/user/hand/right/input/aim/pose");
- }
-
- if (interactionProfilePath == interactionProfilePathKHRSimple) {
- bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/select/click");
- bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/select/click");
- bindings[currBinding++] = ActionSuggestedBinding(buttonAAction, "/user/hand/left/input/menu/click");
- bindings[currBinding++] = ActionSuggestedBinding(buttonXAction, "/user/hand/right/input/menu/click");
- bindings[currBinding++] = ActionSuggestedBinding(vibrateLeftFeedback, "/user/hand/left/output/haptic");
- bindings[currBinding++] = ActionSuggestedBinding(vibrateRightFeedback, "/user/hand/right/output/haptic");
- bindings[currBinding++] = ActionSuggestedBinding(handPoseLeftAction, "/user/hand/left/input/aim/pose");
- bindings[currBinding++] = ActionSuggestedBinding(handPoseRightAction, "/user/hand/right/input/aim/pose");
- }
- }
-
- XrInteractionProfileSuggestedBinding suggestedBindings = {};
- suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
- suggestedBindings.next = NULL;
- suggestedBindings.interactionProfile = interactionProfilePath;
- suggestedBindings.suggestedBindings = bindings;
- suggestedBindings.countSuggestedBindings = currBinding;
- OXR(xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings));
-
- // Attach actions
- XrSessionActionSetsAttachInfo attachInfo = {};
- attachInfo.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO;
- attachInfo.next = NULL;
- attachInfo.countActionSets = 1;
- attachInfo.actionSets = &runningActionSet;
- OXR(xrAttachSessionActionSets(engine->appState.Session, &attachInfo));
-
- // Enumerate actions
- XrPath actionPathsBuffer[32];
- char stringBuffer[256];
- XrAction actionsToEnumerate[] = {
- indexLeftAction,
- indexRightAction,
- menuAction,
- buttonAAction,
- buttonBAction,
- buttonXAction,
- buttonYAction,
- gripLeftAction,
- gripRightAction,
- moveOnLeftJoystickAction,
- moveOnRightJoystickAction,
- thumbstickLeftClickAction,
- thumbstickRightClickAction,
- vibrateLeftFeedback,
- vibrateRightFeedback,
- handPoseLeftAction,
- handPoseRightAction
- };
- for (XrAction & i : actionsToEnumerate) {
- XrBoundSourcesForActionEnumerateInfo enumerateInfo = {};
- enumerateInfo.type = XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO;
- enumerateInfo.next = NULL;
- enumerateInfo.action = i;
-
- // Get Count
- uint32_t countOutput = 0;
+ if (countOutput < 32) {
OXR(xrEnumerateBoundSourcesForAction(
- engine->appState.Session, &enumerateInfo, 0 /* request size */, &countOutput, NULL));
- ALOGV(
- "xrEnumerateBoundSourcesForAction action=%lld count=%u",
- (long long)enumerateInfo.action,
- countOutput);
+ engine->appState.Session, &enumerateInfo, 32, &countOutput, actionPathsBuffer));
+ for (uint32_t a = 0; a < countOutput; ++a) {
+ XrInputSourceLocalizedNameGetInfo nameGetInfo = {};
+ nameGetInfo.type = XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO;
+ nameGetInfo.next = NULL;
+ nameGetInfo.sourcePath = actionPathsBuffer[a];
+ nameGetInfo.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT |
+ XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT |
+ XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT;
- if (countOutput < 32) {
- OXR(xrEnumerateBoundSourcesForAction(
- engine->appState.Session, &enumerateInfo, 32, &countOutput, actionPathsBuffer));
- for (uint32_t a = 0; a < countOutput; ++a) {
- XrInputSourceLocalizedNameGetInfo nameGetInfo = {};
- nameGetInfo.type = XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO;
- nameGetInfo.next = NULL;
- nameGetInfo.sourcePath = actionPathsBuffer[a];
- nameGetInfo.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT |
- XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT |
- XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT;
-
- uint32_t stringCount = 0u;
- OXR(xrGetInputSourceLocalizedName(
- engine->appState.Session, &nameGetInfo, 0, &stringCount, NULL));
- if (stringCount < 256) {
- OXR(xrGetInputSourceLocalizedName(
- engine->appState.Session, &nameGetInfo, 256, &stringCount, stringBuffer));
- char pathStr[256];
- uint32_t strLen = 0;
- OXR(xrPathToString(
- engine->appState.Instance,
- actionPathsBuffer[a],
- (uint32_t)sizeof(pathStr),
- &strLen,
- pathStr));
- ALOGV(
- " -> path = %lld `%s` -> `%s`",
- (long long)actionPathsBuffer[a],
- pathStr,
- stringBuffer);
- }
+ uint32_t stringCount = 0u;
+ OXR(xrGetInputSourceLocalizedName(engine->appState.Session, &nameGetInfo, 0, &stringCount, NULL));
+ if (stringCount < 256) {
+ OXR(xrGetInputSourceLocalizedName(engine->appState.Session, &nameGetInfo, 256, &stringCount, stringBuffer));
+ char pathStr[256];
+ uint32_t strLen = 0;
+ OXR(xrPathToString(engine->appState.Instance, actionPathsBuffer[a], (uint32_t)sizeof(pathStr), &strLen, pathStr));
+ ALOGV(" -> path = %lld `%s` -> `%s`", (long long)actionPathsBuffer[a], pathStr, stringBuffer);
}
}
}
diff --git a/Common/VR/VRRenderer.cpp b/Common/VR/VRRenderer.cpp
index f9a8e5ce53..4d41d09bcd 100644
--- a/Common/VR/VRRenderer.cpp
+++ b/Common/VR/VRRenderer.cpp
@@ -6,12 +6,14 @@
#include
#include
+XrFovf fov;
XrView* projections;
XrPosef invViewTransform[2];
XrFrameState frameState = {};
bool initialized = false;
bool stageSupported = false;
int vrConfig[VR_CONFIG_MAX] = {};
+ovrMatrix4f vrMatrix[VR_MATRIX_COUNT];
XrVector3f hmdorientation;
XrVector3f hmdposition;
@@ -279,7 +281,12 @@ bool VR_InitFrame( engine_t* engine ) {
projections));
//
+ fov = {};
for (int eye = 0; eye < ovrMaxNumEyes; eye++) {
+ fov.angleLeft += projections[eye].fov.angleLeft / 2.0f;
+ fov.angleRight += projections[eye].fov.angleRight / 2.0f;
+ fov.angleUp += projections[eye].fov.angleUp / 2.0f;
+ fov.angleDown += projections[eye].fov.angleDown / 2.0f;
invViewTransform[eye] = projections[eye].pose;
}
@@ -290,6 +297,61 @@ bool VR_InitFrame( engine_t* engine ) {
engine->appState.LayerCount = 0;
memset(engine->appState.Layers, 0, sizeof(ovrCompositorLayer_Union) * ovrMaxLayerCount);
+
+ // Update matrices
+ for (int matrix = 0; matrix < VR_MATRIX_COUNT; matrix++) {
+ if ((matrix == VR_PROJECTION_MATRIX_LEFT_EYE) || (matrix == VR_PROJECTION_MATRIX_RIGHT_EYE)) {
+ float near = (float)vrConfig[VR_CONFIG_FOV_SCALE] / 200.0f;
+ vrMatrix[matrix] = ovrMatrix4f_CreateProjectionFov(fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown, near, 0.0f );
+ } else if ((matrix == VR_VIEW_MATRIX_LEFT_EYE) || (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) {
+ XrPosef invView = invViewTransform[0];
+
+ // get axis mirroring configuration
+ float mx = vrConfig[VR_CONFIG_MIRROR_PITCH] ? -1 : 1;
+ float my = vrConfig[VR_CONFIG_MIRROR_YAW] ? -1 : 1;
+ float mz = vrConfig[VR_CONFIG_MIRROR_ROLL] ? -1 : 1;
+
+ // ensure there is maximally one axis to mirror rotation
+ if (mx + my + mz < 0) {
+ mx *= -1.0f;
+ my *= -1.0f;
+ mz *= -1.0f;
+ } else {
+ invView = XrPosef_Inverse(invView);
+ }
+
+ // create updated quaternion
+ if (mx + my + mz < 3 - EPSILON) {
+ XrVector3f rotation = XrQuaternionf_ToEulerAngles(invView.orientation);
+ XrQuaternionf pitch = XrQuaternionf_CreateFromVectorAngle({1, 0, 0}, mx * ToRadians(rotation.x));
+ XrQuaternionf yaw = XrQuaternionf_CreateFromVectorAngle({0, 1, 0}, my * ToRadians(rotation.y));
+ XrQuaternionf roll = XrQuaternionf_CreateFromVectorAngle({0, 0, 1}, mz * ToRadians(rotation.z));
+ invView.orientation = XrQuaternionf_Multiply(roll, XrQuaternionf_Multiply(pitch, yaw));
+ }
+
+ vrMatrix[matrix] = ovrMatrix4f_CreateFromQuaternion(&invView.orientation);
+ float scale = (float)VR_GetConfig(VR_CONFIG_6DOF_SCALE) * 0.000001f;
+ if (vrConfig[VR_CONFIG_6DOF_ENABLED]) {
+ vrMatrix[matrix].M[0][3] -= hmdposition.x * (vrConfig[VR_CONFIG_MIRROR_AXIS_X] ? -1.0f : 1.0f) * scale;
+ vrMatrix[matrix].M[1][3] -= hmdposition.y * (vrConfig[VR_CONFIG_MIRROR_AXIS_Y] ? -1.0f : 1.0f) * scale;
+ vrMatrix[matrix].M[2][3] -= hmdposition.z * (vrConfig[VR_CONFIG_MIRROR_AXIS_Z] ? -1.0f : 1.0f) * scale;
+ }
+ if (vrConfig[VR_CONFIG_6DOF_PRECISE] && (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) {
+ float dx = fabs(invViewTransform[1].position.x - invViewTransform[0].position.x);
+ float dy = fabs(invViewTransform[1].position.y - invViewTransform[0].position.y);
+ float dz = fabs(invViewTransform[1].position.z - invViewTransform[0].position.z);
+ float ipd = sqrt(dx * dx + dy * dy + dz * dz);
+ XrVector3f separation = {ipd * scale, 0.0f, 0.0f};
+ separation = XrQuaternionf_Rotate(invView.orientation, separation);
+ separation = XrVector3f_ScalarMultiply(separation, vrConfig[VR_CONFIG_MIRROR_AXIS_Z] ? -1.0f : 1.0f);
+ vrMatrix[matrix].M[0][3] -= separation.x;
+ vrMatrix[matrix].M[1][3] -= separation.y;
+ vrMatrix[matrix].M[2][3] -= separation.z;
+ }
+ } else {
+ assert(false);
+ }
+ }
return true;
}
@@ -326,16 +388,17 @@ void VR_FinishFrame( engine_t* engine ) {
for (int eye = 0; eye < ovrMaxNumEyes; eye++) {
int imageLayer = engine->appState.Renderer.Multiview ? eye : 0;
ovrFramebuffer* frameBuffer = &engine->appState.Renderer.FrameBuffer[0];
- XrFovf fov = projections[eye].fov;
- if (vrMode == VR_MODE_MONO_6DOF) {
- fov = projections[0].fov;
- } else if (!engine->appState.Renderer.Multiview) {
- frameBuffer = &engine->appState.Renderer.FrameBuffer[eye];
+ XrPosef pose = invViewTransform[0];
+ if (vrMode != VR_MODE_MONO_6DOF) {
+ if (!engine->appState.Renderer.Multiview) {
+ frameBuffer = &engine->appState.Renderer.FrameBuffer[eye];
+ }
+ pose = invViewTransform[eye];
}
memset(&projection_layer_elements[eye], 0, sizeof(XrCompositionLayerProjectionView));
projection_layer_elements[eye].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
- projection_layer_elements[eye].pose = invViewTransform[eye];
+ projection_layer_elements[eye].pose = pose;
projection_layer_elements[eye].fov = fov;
memset(&projection_layer_elements[eye].subImage, 0, sizeof(XrSwapchainSubImage));
@@ -431,51 +494,5 @@ void VR_BindFramebuffer(engine_t *engine) {
}
ovrMatrix4f VR_GetMatrix( VRMatrix matrix ) {
- ovrMatrix4f output;
- if ((matrix == VR_PROJECTION_MATRIX_LEFT_EYE) || (matrix == VR_PROJECTION_MATRIX_RIGHT_EYE)) {
- XrFovf fov = matrix == VR_PROJECTION_MATRIX_LEFT_EYE ? projections[0].fov : projections[1].fov;
- float near = (float)vrConfig[VR_CONFIG_FOV_SCALE] / 200.0f;
- output = ovrMatrix4f_CreateProjectionFov(fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown, near, 0.0f );
- } else if ((matrix == VR_VIEW_MATRIX_LEFT_EYE) || (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) {
- XrPosef invView = invViewTransform[0];
-
- // get axis mirroring configuration
- float mx = vrConfig[VR_CONFIG_MIRROR_PITCH] ? -1 : 1;
- float my = vrConfig[VR_CONFIG_MIRROR_YAW] ? -1 : 1;
- float mz = vrConfig[VR_CONFIG_MIRROR_ROLL] ? -1 : 1;
-
- // ensure there is maximally one axis to mirror rotation
- if (mx + my + mz < 0) {
- mx *= -1.0f;
- my *= -1.0f;
- mz *= -1.0f;
- } else {
- invView = XrPosef_Inverse(invView);
- }
-
- // create updated quaternion
- if (mx + my + mz < 3 - EPSILON) {
- XrVector3f rotation = XrQuaternionf_ToEulerAngles(invView.orientation);
- XrQuaternionf pitch = XrQuaternionf_CreateFromVectorAngle({1, 0, 0}, mx * ToRadians(rotation.x));
- XrQuaternionf yaw = XrQuaternionf_CreateFromVectorAngle({0, 1, 0}, my * ToRadians(rotation.y));
- XrQuaternionf roll = XrQuaternionf_CreateFromVectorAngle({0, 0, 1}, mz * ToRadians(rotation.z));
- invView.orientation = XrQuaternionf_Multiply(roll, XrQuaternionf_Multiply(pitch, yaw));
- }
-
- output = ovrMatrix4f_CreateFromQuaternion(&invView.orientation);
- float scale = (float)VR_GetConfig(VR_CONFIG_6DOF_SCALE) * 0.000001f;
- if (vrConfig[VR_CONFIG_6DOF_ENABLED]) {
- output.M[0][3] -= hmdposition.x * (vrConfig[VR_CONFIG_MIRROR_AXIS_X] ? -1.0f : 1.0f) * scale;
- output.M[1][3] -= hmdposition.y * (vrConfig[VR_CONFIG_MIRROR_AXIS_Y] ? -1.0f : 1.0f) * scale;
- output.M[2][3] -= hmdposition.z * (vrConfig[VR_CONFIG_MIRROR_AXIS_Z] ? -1.0f : 1.0f) * scale;
- }
- if (vrConfig[VR_CONFIG_6DOF_PRECISE] && (matrix == VR_VIEW_MATRIX_RIGHT_EYE)) {
- output.M[0][3] += (invViewTransform[1].position.x - invViewTransform[0].position.x) * scale;
- output.M[1][3] += (invViewTransform[1].position.y - invViewTransform[0].position.y) * scale;
- output.M[2][3] += (invViewTransform[1].position.z - invViewTransform[0].position.z) * scale;
- }
- } else {
- assert(false);
- }
- return output;
+ return vrMatrix[matrix];
}
diff --git a/Common/VR/VRRenderer.h b/Common/VR/VRRenderer.h
index 9375370cad..e19a0e82a5 100644
--- a/Common/VR/VRRenderer.h
+++ b/Common/VR/VRRenderer.h
@@ -26,10 +26,11 @@ enum VRConfig {
};
enum VRMatrix {
- VR_PROJECTION_MATRIX_LEFT_EYE = 0,
- VR_PROJECTION_MATRIX_RIGHT_EYE = 1,
- VR_VIEW_MATRIX_LEFT_EYE = 2,
- VR_VIEW_MATRIX_RIGHT_EYE = 3
+ VR_PROJECTION_MATRIX_LEFT_EYE,
+ VR_PROJECTION_MATRIX_RIGHT_EYE,
+ VR_VIEW_MATRIX_LEFT_EYE,
+ VR_VIEW_MATRIX_RIGHT_EYE,
+ VR_MATRIX_COUNT
};
enum VRMode {
diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp
index 75c0623ead..861e926429 100644
--- a/Core/Compatibility.cpp
+++ b/Core/Compatibility.cpp
@@ -46,6 +46,7 @@ void Compatibility::Load(const std::string &gameID) {
IniFile compat;
// This loads from assets.
if (compat.LoadFromVFS("compatvr.ini")) {
+ CheckSetting(compat, gameID, "Skyplane", &vrCompat_.Skyplane);
CheckSetting(compat, gameID, "UnitsPerMeter", &vrCompat_.UnitsPerMeter);
}
}
@@ -96,7 +97,6 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) {
CheckSetting(iniFile, gameID, "DateLimited", &flags_.DateLimited);
CheckSetting(iniFile, gameID, "ShaderColorBitmask", &flags_.ShaderColorBitmask);
CheckSetting(iniFile, gameID, "DisableFirstFrameReadback", &flags_.DisableFirstFrameReadback);
- CheckSetting(iniFile, gameID, "DisableRangeCulling", &flags_.DisableRangeCulling);
CheckSetting(iniFile, gameID, "MpegAvcWarmUp", &flags_.MpegAvcWarmUp);
CheckSetting(iniFile, gameID, "BlueToAlpha", &flags_.BlueToAlpha);
CheckSetting(iniFile, gameID, "CenteredLines", &flags_.CenteredLines);
@@ -108,6 +108,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) {
CheckSetting(iniFile, gameID, "SplitFramebufferMargin", &flags_.SplitFramebufferMargin);
CheckSetting(iniFile, gameID, "ForceLowerResolutionForEffectsOn", &flags_.ForceLowerResolutionForEffectsOn);
CheckSetting(iniFile, gameID, "AllowDownloadCLUT", &flags_.AllowDownloadCLUT);
+ CheckSetting(iniFile, gameID, "NearestFilteringOnFramebufferCreate", &flags_.NearestFilteringOnFramebufferCreate);
}
void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) {
diff --git a/Core/Compatibility.h b/Core/Compatibility.h
index 04f3ab22b7..8bbdc52ebd 100644
--- a/Core/Compatibility.h
+++ b/Core/Compatibility.h
@@ -77,7 +77,6 @@ struct CompatFlags {
bool DateLimited;
bool ShaderColorBitmask;
bool DisableFirstFrameReadback;
- bool DisableRangeCulling;
bool MpegAvcWarmUp;
bool BlueToAlpha;
bool CenteredLines;
@@ -89,9 +88,11 @@ struct CompatFlags {
bool SplitFramebufferMargin;
bool ForceLowerResolutionForEffectsOn;
bool AllowDownloadCLUT;
+ bool NearestFilteringOnFramebufferCreate;
};
struct VRCompat {
+ bool Skyplane;
float UnitsPerMeter;
};
diff --git a/Core/Config.cpp b/Core/Config.cpp
index 27f97e89c5..4eeca428ab 100644
--- a/Core/Config.cpp
+++ b/Core/Config.cpp
@@ -610,6 +610,7 @@ static ConfigSetting generalSettings[] = {
ConfigSetting("PauseWhenMinimized", &g_Config.bPauseWhenMinimized, false, true, true),
ConfigSetting("DumpDecryptedEboots", &g_Config.bDumpDecryptedEboot, false, true, true),
ConfigSetting("FullscreenOnDoubleclick", &g_Config.bFullscreenOnDoubleclick, true, false, false),
+ ConfigSetting("ShowMenuBar", &g_Config.bShowMenuBar, true, true, false),
ReportedConfigSetting("MemStickInserted", &g_Config.bMemStickInserted, true, true, true),
ConfigSetting("EnablePlugins", &g_Config.bLoadPlugins, true, true, true),
@@ -873,6 +874,7 @@ static ConfigSetting graphicsSettings[] = {
#endif
ConfigSetting("CameraDevice", &g_Config.sCameraDevice, "", true, false),
ConfigSetting("VendorBugChecksEnabled", &g_Config.bVendorBugChecksEnabled, true, false, false),
+ ConfigSetting("UseGeometryShader", &g_Config.bUseGeometryShader, true, true, true),
ReportedConfigSetting("RenderingMode", &g_Config.iRenderingMode, 1, true, true),
ConfigSetting("SoftwareRenderer", &g_Config.bSoftwareRendering, false, true, true),
ConfigSetting("SoftwareRendererJit", &g_Config.bSoftwareRenderingJit, true, true, true),
@@ -938,7 +940,6 @@ static ConfigSetting graphicsSettings[] = {
ReportedConfigSetting("FragmentTestCache", &g_Config.bFragmentTestCache, true, true, true),
ConfigSetting("GfxDebugOutput", &g_Config.bGfxDebugOutput, false, false, false),
- ConfigSetting("GfxDebugSplitSubmit", &g_Config.bGfxDebugSplitSubmit, false, false, false),
ConfigSetting("LogFrameDrops", &g_Config.bLogFrameDrops, false, true, false),
ConfigSetting("InflightFrames", &g_Config.iInflightFrames, 3, true, false),
@@ -1424,7 +1425,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
// build of PPSSPP, receive an upgrade notice, then start a newer version, and still receive the upgrade notice,
// even if said newer version is >= the upgrade found online.
if ((dismissedVersion == upgradeVersion) || (versionsValid && (installed >= upgrade))) {
- upgradeMessage = "";
+ upgradeMessage.clear();
}
// Check for new version on every 10 runs.
@@ -1636,16 +1637,16 @@ void Config::DownloadCompletedCallback(http::Download &download) {
if (installed >= upgrade) {
INFO_LOG(LOADER, "Version check: Already up to date, erasing any upgrade message");
- g_Config.upgradeMessage = "";
+ g_Config.upgradeMessage.clear();
g_Config.upgradeVersion = upgrade.ToString();
- g_Config.dismissedVersion = "";
+ g_Config.dismissedVersion.clear();
return;
}
if (installed < upgrade && dismissed != upgrade) {
g_Config.upgradeMessage = "New version of PPSSPP available!";
g_Config.upgradeVersion = upgrade.ToString();
- g_Config.dismissedVersion = "";
+ g_Config.dismissedVersion.clear();
}
}
diff --git a/Core/Config.h b/Core/Config.h
index 6ae172bfba..65eaf74099 100644
--- a/Core/Config.h
+++ b/Core/Config.h
@@ -164,6 +164,7 @@ public:
bool bHardwareTransform; // only used in the GLES backend
bool bSoftwareSkinning; // may speed up some games
bool bVendorBugChecksEnabled;
+ bool bUseGeometryShader;
int iRenderingMode; // 0 = non-buffered rendering 1 = buffered rendering
int iTexFiltering; // 1 = auto , 2 = nearest , 3 = linear , 4 = auto max quality
@@ -176,6 +177,7 @@ public:
bool bSustainedPerformanceMode; // Android: Slows clocks down to avoid overheating/speed fluctuations.
bool bIgnoreScreenInsets; // Android: Center screen disregarding insets if this is enabled.
bool bVSync;
+
int iFrameSkip;
int iFrameSkipType;
int iFastForwardMode; // See FastForwardMode in ConfigValues.h.
@@ -190,6 +192,7 @@ public:
int iWindowY;
int iWindowWidth; // Windows and other windowed environments
int iWindowHeight;
+ bool bShowMenuBar; // Windows-only
float fUITint;
float fUISaturation;
@@ -242,7 +245,6 @@ public:
bool bShaderChainRequires60FPS;
std::string sTextureShaderName;
bool bGfxDebugOutput;
- bool bGfxDebugSplitSubmit;
int iInflightFrames;
bool bRenderDuplicateFrames;
diff --git a/Core/Core.cpp b/Core/Core.cpp
index 52a1df8f4c..c1bb28cefd 100644
--- a/Core/Core.cpp
+++ b/Core/Core.cpp
@@ -447,7 +447,7 @@ void Core_MemoryException(u32 address, u32 pc, MemoryExceptionType type) {
ExceptionInfo &e = g_exceptionInfo;
e = {};
e.type = ExceptionType::MEMORY;
- e.info = "";
+ e.info.clear();
e.memory_type = type;
e.address = address;
e.pc = pc;
@@ -483,7 +483,7 @@ void Core_ExecException(u32 address, u32 pc, ExecExceptionType type) {
ExceptionInfo &e = g_exceptionInfo;
e = {};
e.type = ExceptionType::BAD_EXEC_ADDR;
- e.info = "";
+ e.info.clear();
e.exec_type = type;
e.address = address;
e.pc = pc;
@@ -498,7 +498,7 @@ void Core_Break(u32 pc) {
ExceptionInfo &e = g_exceptionInfo;
e = {};
e.type = ExceptionType::BREAK;
- e.info = "";
+ e.info.clear();
e.pc = pc;
if (!g_Config.bIgnoreBadMemAccess) {
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index bdfaea4361..1978f3bcac 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -139,7 +139,7 @@
Level3
..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib
- _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;%(PreprocessorDefinitions)
+ _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)
StreamingSIMDExtensions2
Precise
true
@@ -148,6 +148,7 @@
MultiThreadedDebug
Common/DbgNew.h
ProgramDatabase
+ stdcpp17
true
@@ -157,12 +158,15 @@
../git-version.cpp;%(Outputs)
Updating git-version.cpp
+
+ /ignore:4221 %(AdditionalOptions)
+
Level3
..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib;../ext/zstd/lib
- _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;%(PreprocessorDefinitions)
+ _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)
NotSet
Precise
false
@@ -172,6 +176,7 @@
MultiThreadedDebug
Common/DbgNew.h
ProgramDatabase
+ stdcpp17
true
@@ -181,12 +186,15 @@
../git-version.cpp;%(Outputs)
Updating git-version.cpp
+
+ /ignore:4221 %(AdditionalOptions)
+
Level3
..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib
- _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions)
+ _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_DEBUG;_LIB;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)
NotSet
Precise
false
@@ -196,6 +204,7 @@
MultiThreadedDebug
Common/DbgNew.h
ProgramDatabase
+ stdcpp17
true
@@ -205,12 +214,15 @@
../git-version.cpp;%(Outputs)
Updating git-version.cpp
+
+ /ignore:4221 %(AdditionalOptions)
+
Level3
..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib
- _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions)
+ _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_DEBUG;_LIB;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)
NotSet
Precise
false
@@ -221,6 +233,7 @@
ProgramDatabase
+ stdcpp17
true
@@ -230,6 +243,9 @@
../git-version.cpp;%(Outputs)
Updating git-version.cpp
+
+ /ignore:4221 %(AdditionalOptions)
+
@@ -241,12 +257,13 @@
false
StreamingSIMDExtensions2
Precise
- USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;%(PreprocessorDefinitions)
+ USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)
Speed
MultiThreaded
true
false
ProgramDatabase
+ stdcpp17
true
@@ -255,6 +272,7 @@
%(AdditionalDependencies)
+ /ignore:4221 %(AdditionalOptions)
../Windows/git-version-gen.cmd
@@ -276,11 +294,12 @@
Speed
false
true
- USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;%(PreprocessorDefinitions)
+ USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)
false
MultiThreaded
true
ProgramDatabase
+ stdcpp17
true
@@ -292,6 +311,9 @@
../git-version.cpp;%(Outputs)
Updating git-version.cpp
+
+ /ignore:4221 %(AdditionalOptions)
+
@@ -307,11 +329,12 @@
Speed
false
true
- USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_LIB;NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions)
+ USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_LIB;NDEBUG;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)
false
MultiThreaded
true
ProgramDatabase
+ stdcpp17
true
@@ -323,6 +346,9 @@
../git-version.cpp;%(Outputs)
Updating git-version.cpp
+
+ /ignore:4221 %(AdditionalOptions)
+
@@ -338,11 +364,12 @@
Speed
false
true
- USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_LIB;NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions)
+ USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_LIB;NDEBUG;_UNICODE;UNICODE;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)
false
MultiThreaded
true
ProgramDatabase
+ stdcpp17
true
@@ -354,6 +381,9 @@
../git-version.cpp;%(Outputs)
Updating git-version.cpp
+
+ /ignore:4221 %(AdditionalOptions)
+
@@ -568,7 +598,16 @@
-
+
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+
@@ -900,7 +939,16 @@
-
+
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\arm\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+ ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\aarch64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/native/ext/libpng17;../ext/zlib;../ext/native/ext;../ext/armips/;../ext/armips/ext/filesystem/include/;../ext/armips/ext/tinyformat/
+
diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp
index 511aeee82a..ab0e10aa9b 100644
--- a/Core/Debugger/DisassemblyManager.cpp
+++ b/Core/Debugger/DisassemblyManager.cpp
@@ -316,7 +316,7 @@ void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLin
dest.params = "Disassembly failure";
} else {
dest.name = "-";
- dest.params = "";
+ dest.params.clear();
}
}
@@ -1003,7 +1003,7 @@ void DisassemblyData::createLines()
lines[currentLineStart] = entry;
lineAddresses.push_back(currentLineStart);
- currentLine = "";
+ currentLine.clear();
currentLineStart = pos-1;
inString = false;
}
@@ -1028,7 +1028,7 @@ void DisassemblyData::createLines()
lines[currentLineStart] = entry;
lineAddresses.push_back(currentLineStart);
- currentLine = "";
+ currentLine.clear();
currentLineStart = pos-1;
inString = false;
}
@@ -1099,7 +1099,7 @@ void DisassemblyData::createLines()
lines[currentLineStart] = entry;
lineAddresses.push_back(currentLineStart);
- currentLine = "";
+ currentLine.clear();
currentLineStart = currentPos;
}
diff --git a/Core/Debugger/MemBlockInfo.cpp b/Core/Debugger/MemBlockInfo.cpp
index e38b6861b7..e361b63014 100644
--- a/Core/Debugger/MemBlockInfo.cpp
+++ b/Core/Debugger/MemBlockInfo.cpp
@@ -399,12 +399,18 @@ void FlushPendingMemInfo() {
pendingNotifyMaxAddr2 = 0;
}
+static inline uint32_t NormalizeAddress(uint32_t addr) {
+ if ((addr & 0x3F000000) == 0x04000000)
+ return addr & 0x041FFFFF;
+ return addr & 0x3FFFFFFF;
+}
+
void NotifyMemInfoPC(MemBlockFlags flags, uint32_t start, uint32_t size, uint32_t pc, const char *tagStr, size_t strLength) {
if (size == 0) {
return;
}
// Clear the uncached and kernel bits.
- start &= ~0xC0000000;
+ start = NormalizeAddress(start);
bool needFlush = false;
// When the setting is off, we skip smaller info to keep things fast.
@@ -450,7 +456,7 @@ void NotifyMemInfo(MemBlockFlags flags, uint32_t start, uint32_t size, const cha
}
std::vector FindMemInfo(uint32_t start, uint32_t size) {
- start &= ~0xC0000000;
+ start = NormalizeAddress(start);
if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start)
FlushPendingMemInfo();
@@ -466,7 +472,7 @@ std::vector FindMemInfo(uint32_t start, uint32_t size) {
}
std::vector FindMemInfoByFlag(MemBlockFlags flags, uint32_t start, uint32_t size) {
- start &= ~0xC0000000;
+ start = NormalizeAddress(start);
if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start)
FlushPendingMemInfo();
@@ -486,7 +492,7 @@ std::vector FindMemInfoByFlag(MemBlockFlags flags, uint32_t start,
}
static const char *FindWriteTagByFlag(MemBlockFlags flags, uint32_t start, uint32_t size) {
- start &= ~0xC0000000;
+ start = NormalizeAddress(start);
if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start)
FlushPendingMemInfo();
diff --git a/Core/Debugger/SymbolMap.cpp b/Core/Debugger/SymbolMap.cpp
index e276e9be27..63e087d7a8 100644
--- a/Core/Debugger/SymbolMap.cpp
+++ b/Core/Debugger/SymbolMap.cpp
@@ -31,6 +31,7 @@
#include
#include
+#include
#include
#include "zlib.h"
@@ -439,7 +440,7 @@ void SymbolMap::AddModule(const char *name, u32 address, u32 size) {
// Just reactivate that one.
it->start = address;
it->size = size;
- activeModuleEnds.insert(std::make_pair(it->start + it->size, *it));
+ activeModuleEnds.emplace(it->start + it->size, *it);
activeNeedUpdate_ = true;
return;
}
@@ -452,7 +453,7 @@ void SymbolMap::AddModule(const char *name, u32 address, u32 size) {
mod.index = (int)modules.size() + 1;
modules.push_back(mod);
- activeModuleEnds.insert(std::make_pair(mod.start + mod.size, mod));
+ activeModuleEnds.emplace(mod.start + mod.size, mod);
activeNeedUpdate_ = true;
}
@@ -559,7 +560,7 @@ void SymbolMap::AddFunction(const char* name, u32 address, u32 size, int moduleI
auto active = activeFunctions.find(address);
if (active != activeFunctions.end() && active->second.module == moduleIndex) {
activeFunctions.erase(active);
- activeFunctions.insert(std::make_pair(address, existing->second));
+ activeFunctions.emplace(address, existing->second);
}
} else {
FunctionEntry func;
@@ -570,7 +571,7 @@ void SymbolMap::AddFunction(const char* name, u32 address, u32 size, int moduleI
functions[symbolKey] = func;
if (IsModuleActive(moduleIndex)) {
- activeFunctions.insert(std::make_pair(address, func));
+ activeFunctions.emplace(address, func);
}
}
@@ -716,27 +717,27 @@ void SymbolMap::UpdateActiveSymbols() {
for (auto it = functions.begin(), end = functions.end(); it != end; ++it) {
const auto mod = activeModuleIndexes.find(it->second.module);
if (it->second.module == 0) {
- activeFunctions.insert(std::make_pair(it->second.start, it->second));
+ activeFunctions.emplace(it->second.start, it->second);
} else if (mod != activeModuleIndexes.end()) {
- activeFunctions.insert(std::make_pair(mod->second + it->second.start, it->second));
+ activeFunctions.emplace(mod->second + it->second.start, it->second);
}
}
for (auto it = labels.begin(), end = labels.end(); it != end; ++it) {
const auto mod = activeModuleIndexes.find(it->second.module);
if (it->second.module == 0) {
- activeLabels.insert(std::make_pair(it->second.addr, it->second));
+ activeLabels.emplace(it->second.addr, it->second);
} else if (mod != activeModuleIndexes.end()) {
- activeLabels.insert(std::make_pair(mod->second + it->second.addr, it->second));
+ activeLabels.emplace(mod->second + it->second.addr, it->second);
}
}
for (auto it = data.begin(), end = data.end(); it != end; ++it) {
const auto mod = activeModuleIndexes.find(it->second.module);
if (it->second.module == 0) {
- activeData.insert(std::make_pair(it->second.start, it->second));
+ activeData.emplace(it->second.start, it->second);
} else if (mod != activeModuleIndexes.end()) {
- activeData.insert(std::make_pair(mod->second + it->second.start, it->second));
+ activeData.emplace(mod->second + it->second.start, it->second);
}
}
@@ -757,7 +758,7 @@ bool SymbolMap::SetFunctionSize(u32 startAddress, u32 newSize) {
if (func != functions.end()) {
func->second.size = newSize;
activeFunctions.erase(funcInfo);
- activeFunctions.insert(std::make_pair(startAddress, func->second));
+ activeFunctions.emplace(startAddress, func->second);
}
}
@@ -829,7 +830,7 @@ void SymbolMap::AddLabel(const char* name, u32 address, int moduleIndex) {
auto active = activeLabels.find(address);
if (active != activeLabels.end() && active->second.module == moduleIndex) {
activeLabels.erase(active);
- activeLabels.insert(std::make_pair(address, label));
+ activeLabels.emplace(address, label);
}
}
} else {
@@ -840,7 +841,7 @@ void SymbolMap::AddLabel(const char* name, u32 address, int moduleIndex) {
labels[symbolKey] = label;
if (IsModuleActive(moduleIndex)) {
- activeLabels.insert(std::make_pair(address, label));
+ activeLabels.emplace(address, label);
}
}
}
@@ -864,7 +865,7 @@ void SymbolMap::SetLabelName(const char* name, u32 address) {
auto active = activeLabels.find(address);
if (active != activeLabels.end() && active->second.module == label->second.module) {
activeLabels.erase(active);
- activeLabels.insert(std::make_pair(address, label->second));
+ activeLabels.emplace(address, label->second);
}
}
}
@@ -947,7 +948,7 @@ void SymbolMap::AddData(u32 address, u32 size, DataType type, int moduleIndex) {
auto active = activeData.find(address);
if (active != activeData.end() && active->second.module == moduleIndex) {
activeData.erase(active);
- activeData.insert(std::make_pair(address, existing->second));
+ activeData.emplace(address, existing->second);
}
} else {
DataEntry entry;
@@ -958,7 +959,7 @@ void SymbolMap::AddData(u32 address, u32 size, DataType type, int moduleIndex) {
data[symbolKey] = entry;
if (IsModuleActive(moduleIndex)) {
- activeData.insert(std::make_pair(address, entry));
+ activeData.emplace(address, entry);
}
}
}
@@ -1037,8 +1038,9 @@ void SymbolMap::GetLabels(std::vector &dest) {
for (auto it = activeLabels.begin(); it != activeLabels.end(); it++) {
LabelDefinition entry;
entry.value = it->first;
- entry.name = ConvertUTF8ToWString(it->second.name);
- std::transform(entry.name.begin(), entry.name.end(), entry.name.begin(), ::towlower);
+ std::string name = it->second.name;
+ std::transform(name.begin(), name.end(), name.begin(), ::tolower);
+ entry.name = Identifier(name);
dest.push_back(entry);
}
}
diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp
index b71db06877..23333d5a19 100644
--- a/Core/Debugger/WebSocket.cpp
+++ b/Core/Debugger/WebSocket.cpp
@@ -97,6 +97,10 @@ static void UpdateConnected(int delta) {
}
static void WebSocketNotifyLifecycle(CoreLifecycle stage) {
+ // We'll likely already be locked during the reboot.
+ if (PSP_IsRebooting())
+ return;
+
switch (stage) {
case CoreLifecycle::STARTING:
case CoreLifecycle::STOPPING:
diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp
index 79a0fe70a8..5d766785a2 100644
--- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp
+++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp
@@ -379,7 +379,7 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) {
json.pop();
}
-// Search disassembly for some text (cpu.searchDisasm)
+// Search disassembly for some text (memory.searchDisasm)
//
// Parameters:
// - thread: optional number indicating the thread id (may not affect search much.)
@@ -461,7 +461,7 @@ void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) {
json.writeNull("address");
}
-// Assemble an instruction (cpu.assemble)
+// Assemble an instruction (memory.assemble)
//
// Parameters:
// - address: number indicating the address to write to.
@@ -482,7 +482,7 @@ void WebSocketDisasmState::Assemble(DebuggerRequest &req) {
return;
if (!MIPSAsm::MipsAssembleOpcode(code.c_str(), currentDebugMIPS, address))
- return req.Fail(StringFromFormat("Could not assemble: %s", ConvertWStringToUTF8(MIPSAsm::GetAssembleError()).c_str()));
+ return req.Fail(StringFromFormat("Could not assemble: %s", MIPSAsm::GetAssembleError().c_str()));
JsonWriter &json = req.Respond();
Reporting::NotifyDebugger();
diff --git a/Core/Debugger/WebSocket/GameBroadcaster.cpp b/Core/Debugger/WebSocket/GameBroadcaster.cpp
index 3045e70fc4..c8b4039769 100644
--- a/Core/Debugger/WebSocket/GameBroadcaster.cpp
+++ b/Core/Debugger/WebSocket/GameBroadcaster.cpp
@@ -86,7 +86,7 @@ void GameBroadcaster::Broadcast(net::WebSocketServer *ws) {
} else if (state == UISTATE_INGAME && PSP_IsInited()) {
ws->Send(GameStatusEvent{"game.start"});
prevState_ = state;
- } else if (state == UISTATE_MENU && !PSP_IsInited() && !PSP_IsQuitting()) {
+ } else if (state == UISTATE_MENU && !PSP_IsInited() && !PSP_IsQuitting() && !PSP_IsRebooting()) {
ws->Send(GameStatusEvent{"game.quit"});
prevState_ = state;
}
diff --git a/Core/Debugger/WebSocket/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp
index 8cb9fcfc69..c48f396bb7 100644
--- a/Core/Debugger/WebSocket/GameSubscriber.cpp
+++ b/Core/Debugger/WebSocket/GameSubscriber.cpp
@@ -49,9 +49,8 @@ void WebSocketGameReset(DebuggerRequest &req) {
if (needBreak)
PSP_CoreParameter().startBreak = true;
- PSP_Shutdown();
std::string resetError;
- if (!PSP_Init(PSP_CoreParameter(), &resetError)) {
+ if (!PSP_Reboot(&resetError)) {
ERROR_LOG(BOOT, "Error resetting: %s", resetError.c_str());
return req.Fail("Could not reset");
}
diff --git a/Core/Debugger/WebSocket/HLESubscriber.cpp b/Core/Debugger/WebSocket/HLESubscriber.cpp
index fe3cde43ef..2e4a7be55e 100644
--- a/Core/Debugger/WebSocket/HLESubscriber.cpp
+++ b/Core/Debugger/WebSocket/HLESubscriber.cpp
@@ -22,6 +22,7 @@
#include "Core/Debugger/SymbolMap.h"
#include "Core/Debugger/WebSocket/HLESubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
+#include "Core/MemMap.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/MIPS/MIPSStackWalk.h"
@@ -36,6 +37,7 @@ DebuggerSubscriber *WebSocketHLEInit(DebuggerEventHandlerMap &map) {
map["hle.func.add"] = &WebSocketHLEFuncAdd;
map["hle.func.remove"] = &WebSocketHLEFuncRemove;
map["hle.func.rename"] = &WebSocketHLEFuncRename;
+ map["hle.func.scan"] = &WebSocketHLEFuncScan;
map["hle.module.list"] = &WebSocketHLEModuleList;
map["hle.backtrace"] = &WebSocketHLEBacktrace;
@@ -403,6 +405,35 @@ void WebSocketHLEFuncRename(DebuggerRequest &req) {
json.writeString("name", name);
}
+// Auto-detect functions in a memory range (hle.func.scan)
+//
+// Parameters:
+// - address: unsigned integer address within function to rename.
+// - size: unsigned integer size in bytes for scan.
+//
+// Response (same event name) with no extra data.
+void WebSocketHLEFuncScan(DebuggerRequest &req) {
+ if (!g_symbolMap)
+ return req.Fail("CPU not active");
+ if (!Core_IsStepping())
+ return req.Fail("CPU currently running (cpu.stepping first)");
+
+ u32 addr;
+ if (!req.ParamU32("address", &addr))
+ return;
+ u32 size;
+ if (!req.ParamU32("size", &size))
+ return;
+
+ if (!Memory::IsValidRange(addr, size))
+ return req.Fail("Address or size outside valid memory");
+
+ bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size, true);
+ MIPSAnalyst::FinalizeScan(insertSymbols);
+
+ req.Respond();
+}
+
// List all known user modules (hle.module.list)
//
// No parameters.
diff --git a/Core/Debugger/WebSocket/HLESubscriber.h b/Core/Debugger/WebSocket/HLESubscriber.h
index 6d473c3563..e70b4c8ff0 100644
--- a/Core/Debugger/WebSocket/HLESubscriber.h
+++ b/Core/Debugger/WebSocket/HLESubscriber.h
@@ -28,5 +28,6 @@ void WebSocketHLEFuncList(DebuggerRequest &req);
void WebSocketHLEFuncAdd(DebuggerRequest &req);
void WebSocketHLEFuncRemove(DebuggerRequest &req);
void WebSocketHLEFuncRename(DebuggerRequest &req);
+void WebSocketHLEFuncScan(DebuggerRequest &req);
void WebSocketHLEModuleList(DebuggerRequest &req);
void WebSocketHLEBacktrace(DebuggerRequest &req);
diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp
index cc49e33336..3ba5984f4b 100644
--- a/Core/Debugger/WebSocket/WebSocketUtils.cpp
+++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp
@@ -216,7 +216,7 @@ bool DebuggerRequest::ParamBool(const char *name, bool *out, DebuggerParamType t
*out = true;
return true;
}
- if (s == "0" || s == "false" || (s == "" && allowLoose)) {
+ if (s == "0" || s == "false" || (s.empty() && allowLoose)) {
*out = false;
return true;
}
@@ -262,7 +262,7 @@ bool DebuggerRequest::ParamString(const char *name, std::string *out, DebuggerPa
return true;
} else if (tag == JSON_NULL) {
if (required) {
- *out = "";
+ out->clear();
}
return true;
} else if (tag == JSON_NUMBER) {
diff --git a/Core/Dialog/PSPGamedataInstallDialog.cpp b/Core/Dialog/PSPGamedataInstallDialog.cpp
index 9fe30db3c8..3bbe9442f1 100644
--- a/Core/Dialog/PSPGamedataInstallDialog.cpp
+++ b/Core/Dialog/PSPGamedataInstallDialog.cpp
@@ -36,8 +36,9 @@ const static u32 GAMEDATA_BYTES_PER_READ = 32768;
// If this is too high, some games (e.g. Senjou no Valkyria 3) will lag.
const static u32 GAMEDATA_READS_PER_UPDATE = 20;
-const u32 ERROR_UTILITY_GAMEDATA_MEMSTRICK_WRITE_PROTECTED = 0x80111903;
const u32 ERROR_UTILITY_GAMEDATA_MEMSTRICK_REMOVED = 0x80111901;
+const u32 ERROR_UTILITY_GAMEDATA_MEMSTRICK_WRITE_PROTECTED = 0x80111903;
+const u32 ERROR_UTILITY_GAMEDATA_INVALID_MODE = 0x80111908;
static const std::string SFO_FILENAME = "PARAM.SFO";
@@ -88,9 +89,14 @@ int PSPGamedataInstallDialog::Init(u32 paramAddr) {
}
int size = Memory::Read_U32(paramAddr);
+ if (size != 1424 && size != 1432) {
+ ERROR_LOG_REPORT(SCEUTILITY, "sceGamedataInstallInitStart: invalid param size %d", size);
+ return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE;
+ }
+
memset(&request, 0, sizeof(request));
// Only copy the right size to support different request format
- Memory::Memcpy(&request, paramAddr, size);
+ Memory::Memcpy(&request, paramAddr, size, "sceGamedataInstallInitStart");
ChangeStatusInit(GAMEDATA_INIT_DELAY_US);
return 0;
@@ -100,6 +106,17 @@ int PSPGamedataInstallDialog::Update(int animSpeed) {
if (GetStatus() != SCE_UTILITY_STATUS_RUNNING)
return SCE_ERROR_UTILITY_INVALID_STATUS;
+ if (param->mode >= 2) {
+ param->common.result = ERROR_UTILITY_GAMEDATA_INVALID_MODE;
+ param.NotifyWrite("DialogResult");
+ ChangeStatus(SCE_UTILITY_STATUS_FINISHED, 0);
+ WARN_LOG_REPORT(SCEUTILITY, "sceUtilityGamedataInstallUpdate: invalid mode %d", param->mode);
+ return 0;
+ }
+
+ // TODO: param->mode == 1 should show a prompt to confirm, then a progress bar.
+ // Any other mode (i.e. 0 or negative) should proceed and show no UI.
+
// TODO: This should return error codes in some cases, like write failure.
// request.common.result must be updated for errors as well.
@@ -222,6 +239,9 @@ void PSPGamedataInstallDialog::WriteSfoFile() {
}
int PSPGamedataInstallDialog::Abort() {
+ param->common.result = 1;
+ param.NotifyWrite("DialogResult");
+
// TODO: Delete the files or anything?
return PSPDialog::Shutdown();
}
diff --git a/Core/Dialog/PSPGamedataInstallDialog.h b/Core/Dialog/PSPGamedataInstallDialog.h
index e669a31873..a74fdb5254 100644
--- a/Core/Dialog/PSPGamedataInstallDialog.h
+++ b/Core/Dialog/PSPGamedataInstallDialog.h
@@ -22,7 +22,7 @@
struct SceUtilityGamedataInstallParam {
pspUtilityDialogCommon common;
- u32_le unknown1;
+ s32_le mode;
char gameName[13];
char ignore1[3];
char dataName[20];
diff --git a/Core/Dialog/PSPNetconfDialog.cpp b/Core/Dialog/PSPNetconfDialog.cpp
index 1e0535f301..079ff28358 100644
--- a/Core/Dialog/PSPNetconfDialog.cpp
+++ b/Core/Dialog/PSPNetconfDialog.cpp
@@ -194,21 +194,21 @@ void PSPNetconfDialog::DisplayMessage(std::string text1, std::string text2a, std
PPGeScissor(0, (int)(centerY - h2 - 2), 480, (int)(centerY + h2 + 2));
PPGeDrawTextWrapped(text1.c_str(), 240.0f, centerY - h2 - scrollPos_, WRAP_WIDTH, 0, messageStyle);
- if (text2a != "") {
- if (text2b != "")
+ if (!text2a.empty()) {
+ if (!text2b.empty())
PPGeDrawTextWrapped(text2a.c_str(), 240.0f - 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyleRight);
else
PPGeDrawTextWrapped(text2a.c_str(), 240.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyle);
}
- if (text2b != "")
+ if (!text2b.empty())
PPGeDrawTextWrapped(text2b.c_str(), 240.0f + 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyleLeft);
- if (text3a != "") {
- if (text3b != "")
+ if (!text3a.empty()) {
+ if (!text3b.empty())
PPGeDrawTextWrapped(text3a.c_str(), 240.0f - 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyleRight);
else
PPGeDrawTextWrapped(text3a.c_str(), 240.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyle);
}
- if (text3b != "")
+ if (!text3b.empty())
PPGeDrawTextWrapped(text3b.c_str(), 240.0f + 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyleLeft);
PPGeScissorReset();
diff --git a/Core/Dialog/PSPNpSigninDialog.cpp b/Core/Dialog/PSPNpSigninDialog.cpp
index 3c015046db..48245c2008 100644
--- a/Core/Dialog/PSPNpSigninDialog.cpp
+++ b/Core/Dialog/PSPNpSigninDialog.cpp
@@ -191,21 +191,21 @@ void PSPNpSigninDialog::DisplayMessage(std::string text1, std::string text2a, st
PPGeScissor(0, (int)(centerY - h2 - 2), 480, (int)(centerY + h2 + 2));
PPGeDrawTextWrapped(text1.c_str(), 240.0f, centerY - h2 - scrollPos_, WRAP_WIDTH, 0, messageStyle);
- if (text2a != "") {
- if (text2b != "")
+ if (!text2a.empty()) {
+ if (!text2b.empty())
PPGeDrawTextWrapped(text2a.c_str(), 240.0f - 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyleRight);
else
PPGeDrawTextWrapped(text2a.c_str(), 240.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyle);
}
- if (text2b != "")
+ if (!text2b.empty())
PPGeDrawTextWrapped(text2b.c_str(), 240.0f + 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + marginTop, WRAP_WIDTH, 0, messageStyleLeft);
- if (text3a != "") {
- if (text3b != "")
+ if (!text3a.empty()) {
+ if (!text3b.empty())
PPGeDrawTextWrapped(text3a.c_str(), 240.0f - 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyleRight);
else
PPGeDrawTextWrapped(text3a.c_str(), 240.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyle);
}
- if (text3b != "")
+ if (!text3b.empty())
PPGeDrawTextWrapped(text3b.c_str(), 240.0f + 5.0f, centerY - h2 - scrollPos_ + totalHeight1 + totalHeight2 + marginTop, WRAP_WIDTH, 0, messageStyleLeft);
PPGeScissorReset();
diff --git a/Core/Dialog/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp
index 5a04ebc882..67ef655ccf 100644
--- a/Core/Dialog/SavedataParam.cpp
+++ b/Core/Dialog/SavedataParam.cpp
@@ -258,7 +258,7 @@ std::string SavedataParam::GetSaveFilePath(const SceUtilitySavedataParam *param,
inline static std::string FixedToString(const char *str, size_t n)
{
if (!str) {
- return std::string("");
+ return std::string();
} else {
return std::string(str, strnlen(str, n));
}
@@ -353,11 +353,11 @@ int SavedataParam::DeleteData(SceUtilitySavedataParam* param) {
if (!pspFileSystem.GetFileInfo(sfoPath).exists)
return SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;
- if (fileName != "" && !pspFileSystem.GetFileInfo(filePath).exists) {
+ if (!fileName.empty() && !pspFileSystem.GetFileInfo(filePath).exists) {
return SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND;
}
- if (fileName == "") {
+ if (fileName.empty()) {
return 0;
}
@@ -533,7 +533,7 @@ int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveD
// copy back save name in request
strncpy(param->saveName, saveDirName.c_str(), 20);
- if (fileName == "") {
+ if (fileName.empty()) {
delete[] cryptedData;
} else {
if (!WritePSPFile(filePath, data_, saveSize)) {
@@ -583,13 +583,12 @@ int SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &saveD
std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));
std::string fileName = GetFileName(param);
std::string filePath = dirPath + "/" + fileName;
- std::string sfoPath = dirPath + "/" + SFO_FILENAME;
if (!pspFileSystem.GetFileInfo(dirPath).exists) {
return isRWMode ? SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA : SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;
}
- if (fileName != "" && !pspFileSystem.GetFileInfo(filePath).exists) {
+ if (!fileName.empty() && !pspFileSystem.GetFileInfo(filePath).exists) {
return isRWMode ? SCE_UTILITY_SAVEDATA_ERROR_RW_FILE_NOT_FOUND : SCE_UTILITY_SAVEDATA_ERROR_LOAD_FILE_NOT_FOUND;
}
@@ -637,7 +636,7 @@ int SavedataParam::LoadSaveData(SceUtilitySavedataParam *param, const std::strin
std::string filename = GetFileName(param);
std::string filePath = dirPath + "/" + filename;
// Blank filename always means success, if secureVersion was correct.
- if (filename == "")
+ if (filename.empty())
return 0;
s64 readSize;
@@ -1576,7 +1575,7 @@ void SavedataParam::SetFileInfo(SaveFileInfo &saveInfo, PSPFileInfo &info, std::
saveInfo.idx = 0;
saveInfo.modif_time = info.mtime;
- std::string saveDir = savrDir == "" ? GetGameName(pspParam) + saveName : savrDir;
+ std::string saveDir = savrDir.empty() ? GetGameName(pspParam) + saveName : savrDir;
saveInfo.saveDir = saveDir;
// Start with a blank slate.
diff --git a/Core/ELF/ElfReader.cpp b/Core/ELF/ElfReader.cpp
index abfd881e87..980e427e83 100644
--- a/Core/ELF/ElfReader.cpp
+++ b/Core/ELF/ElfReader.cpp
@@ -583,7 +583,8 @@ int ElfReader::LoadInto(u32 loadAddress, bool fromTop)
{
if (!(sections[sectionToModify].sh_flags & SHF_ALLOC))
{
- ERROR_LOG_REPORT(LOADER, "Trying to relocate non-loaded section %s, ignoring", GetSectionName(sectionToModify));
+ // Generally stuff like debug info. We don't need it.
+ INFO_LOG(LOADER, "Skipping relocation of non-loaded section %s", GetSectionName(sectionToModify));
continue;
}
}
diff --git a/Core/ELF/ParamSFO.cpp b/Core/ELF/ParamSFO.cpp
index 71d07dcaca..a2c002f46e 100644
--- a/Core/ELF/ParamSFO.cpp
+++ b/Core/ELF/ParamSFO.cpp
@@ -283,7 +283,7 @@ std::string ParamSFOData::GenerateFakeID(std::string filename) {
// Generates fake gameID for homebrew based on it's folder name.
// Should probably not be a part of ParamSFO, but it'll be called in same places.
std::string file = PSP_CoreParameter().fileToStart.ToString();
- if (filename != "")
+ if (!filename.empty())
file = filename;
std::size_t lslash = file.find_last_of("/");
diff --git a/Core/FileSystems/ISOFileSystem.cpp b/Core/FileSystems/ISOFileSystem.cpp
index a873f3f19b..0d57bf2848 100644
--- a/Core/FileSystems/ISOFileSystem.cpp
+++ b/Core/FileSystems/ISOFileSystem.cpp
@@ -148,7 +148,7 @@ ISOFileSystem::ISOFileSystem(IHandleAllocator *_hAlloc, BlockDevice *_blockDevic
if (!blockDevice->ReadBlock(16, (u8*)&desc))
blockDevice->NotifyReadError();
- entireISO.name = "";
+ entireISO.name.clear();
entireISO.isDirectory = false;
entireISO.startingPosition = 0;
entireISO.size = _blockDevice->GetNumBlocks();
diff --git a/Core/FileSystems/VirtualDiscFileSystem.cpp b/Core/FileSystems/VirtualDiscFileSystem.cpp
index dd41e95855..9a9e0f8c8b 100644
--- a/Core/FileSystems/VirtualDiscFileSystem.cpp
+++ b/Core/FileSystems/VirtualDiscFileSystem.cpp
@@ -81,7 +81,6 @@ void VirtualDiscFileSystem::LoadFileListIndex() {
return;
}
- std::string buf;
static const int MAX_LINE_SIZE = 2048;
char linebuf[MAX_LINE_SIZE]{};
while (fgets(linebuf, MAX_LINE_SIZE, f)) {
@@ -330,7 +329,7 @@ int VirtualDiscFileSystem::OpenFile(std::string filename, FileAccess access, con
entry.size = 0;
entry.startOffset = 0;
- if (filename == "")
+ if (filename.empty())
{
entry.type = VFILETYPE_ISO;
entry.fileIndex = -1;
diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp
index 896bc3cac8..bd0e9d1cac 100644
--- a/Core/HLE/HLE.cpp
+++ b/Core/HLE/HLE.cpp
@@ -864,6 +864,8 @@ void hleDoLogInternal(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, u64 res,
funcName = latestSyscall->name;
funcFlags = latestSyscall->flags;
+ } else {
+ strcpy(formatted_args, "?");
}
const char *fmt;
diff --git a/Core/HLE/HLE.h b/Core/HLE/HLE.h
index d2a74f9d43..0331fb7b9b 100644
--- a/Core/HLE/HLE.h
+++ b/Core/HLE/HLE.h
@@ -189,7 +189,7 @@ T hleDoLog(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, T res, const char *
template
T hleDoLog(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, T res, const char *file, int line, const char *reportTag, char retmask) {
- if (level > MAX_LOGLEVEL || !GenericLogEnabled(level, t)) {
+ if ((level > MAX_LOGLEVEL || !GenericLogEnabled(level, t)) && !reportTag) {
return res;
}
diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp
index dcacbcc5bd..dfcc535df0 100644
--- a/Core/HLE/proAdhoc.cpp
+++ b/Core/HLE/proAdhoc.cpp
@@ -1325,10 +1325,10 @@ void sendChat(std::string chatString) {
if (IsSocketReady((int)metasocket, false, true) > 0) {
int chatResult = send((int)metasocket, (const char*)&chat, sizeof(chat), MSG_NOSIGNAL);
NOTICE_LOG(SCENET, "Send Chat %s to Adhoc Server", chat.message);
- std::string name = g_Config.sNickName.c_str();
+ std::string name = g_Config.sNickName;
std::lock_guard guard(chatLogLock);
- chatLog.push_back(name.substr(0, 8) + ": " + chat.message);
+ chatLog.emplace_back(name.substr(0, 8) + ": " + chat.message);
chatMessageGeneration++;
}
}
@@ -1891,6 +1891,7 @@ uint32_t getLocalIp(int sock) {
static std::vector> InitPrivateIPRanges() {
struct sockaddr_in saNet {}, saMask{};
std::vector> ip_ranges;
+ ip_ranges.reserve(5);
if (1 == inet_pton(AF_INET, "192.168.0.0", &(saNet.sin_addr)) && 1 == inet_pton(AF_INET, "255.255.0.0", &(saMask.sin_addr)))
ip_ranges.push_back({saNet.sin_addr.s_addr, saMask.sin_addr.s_addr});
@@ -1928,7 +1929,7 @@ void getLocalMac(SceNetEtherAddr * addr){
mac[0] &= 0xfc;
}
else
- if (!ParseMacAddress(g_Config.sMACAddress.c_str(), mac)) {
+ if (!ParseMacAddress(g_Config.sMACAddress, mac)) {
ERROR_LOG(SCENET, "Error parsing mac address %s", g_Config.sMACAddress.c_str());
memset(&mac, 0, sizeof(mac));
}
diff --git a/Core/HLE/sceGe.cpp b/Core/HLE/sceGe.cpp
index 81667a448e..c4feedd6c6 100644
--- a/Core/HLE/sceGe.cpp
+++ b/Core/HLE/sceGe.cpp
@@ -518,49 +518,19 @@ u32 sceGeRestoreContext(u32 ctxAddr) {
return 0;
}
-static void __GeCopyMatrix(u32 matrixPtr, float *mtx, u32 size) {
- for (u32 i = 0; i < size / sizeof(float); ++i) {
- Memory::Write_U32(toFloat24(mtx[i]), matrixPtr + i * sizeof(float));
- }
-}
-
static int sceGeGetMtx(int type, u32 matrixPtr) {
- if (!Memory::IsValidAddress(matrixPtr)) {
- ERROR_LOG(SCEGE, "sceGeGetMtx(%d, %08x) - bad matrix ptr", type, matrixPtr);
- return -1;
+ int size = type == GE_MTX_PROJECTION ? 16 : 12;
+ if (!Memory::IsValidRange(matrixPtr, size * sizeof(float))) {
+ return hleLogError(SCEGE, -1, "bad matrix ptr");
}
- INFO_LOG(SCEGE, "sceGeGetMtx(%d, %08x)", type, matrixPtr);
- switch (type) {
- case GE_MTX_BONE0:
- case GE_MTX_BONE1:
- case GE_MTX_BONE2:
- case GE_MTX_BONE3:
- case GE_MTX_BONE4:
- case GE_MTX_BONE5:
- case GE_MTX_BONE6:
- case GE_MTX_BONE7:
- {
- int n = type - GE_MTX_BONE0;
- __GeCopyMatrix(matrixPtr, gstate.boneMatrix + n * 12, 12 * sizeof(float));
- }
- break;
- case GE_MTX_TEXGEN:
- __GeCopyMatrix(matrixPtr, gstate.tgenMatrix, 12 * sizeof(float));
- break;
- case GE_MTX_WORLD:
- __GeCopyMatrix(matrixPtr, gstate.worldMatrix, 12 * sizeof(float));
- break;
- case GE_MTX_VIEW:
- __GeCopyMatrix(matrixPtr, gstate.viewMatrix, 12 * sizeof(float));
- break;
- case GE_MTX_PROJECTION:
- __GeCopyMatrix(matrixPtr, gstate.projMatrix, 16 * sizeof(float));
- break;
- default:
- return SCE_KERNEL_ERROR_INVALID_INDEX;
- }
- return 0;
+ u32_le *dest = (u32_le *)Memory::GetPointerWriteUnchecked(matrixPtr);
+ // Note: this reads the CPU-visible matrix values, which may differ from the actual used values.
+ // They only differ when more DATA commands are sent than are valid for a matrix.
+ if (!gpu || !gpu->GetMatrix24(GEMatrixType(type), dest, 0))
+ return hleLogError(SCEGE, SCE_KERNEL_ERROR_INVALID_INDEX, "invalid matrix");
+
+ return hleLogSuccessInfoI(SCEGE, 0);
}
static u32 sceGeGetCmd(int cmd) {
@@ -600,21 +570,17 @@ static int sceGeGetStack(int index, u32 stackPtr) {
return gpu->GetStack(index, stackPtr);
}
-static u32 sceGeEdramSetAddrTranslation(int new_size) {
+static u32 sceGeEdramSetAddrTranslation(u32 new_size) {
bool outsideRange = new_size != 0 && (new_size < 0x200 || new_size > 0x1000);
bool notPowerOfTwo = (new_size & (new_size - 1)) != 0;
if (outsideRange || notPowerOfTwo) {
- WARN_LOG(SCEGE, "sceGeEdramSetAddrTranslation(%i): invalid value", new_size);
- return SCE_KERNEL_ERROR_INVALID_VALUE;
+ return hleLogWarning(SCEGE, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid value");
+ }
+ if (!gpu) {
+ return hleLogError(SCEGE, -1, "GPUInterface not available");
}
- DEBUG_LOG(SCEGE, "sceGeEdramSetAddrTranslation(%i)", new_size);
-
- // TODO: This isn't safe. EDRamWidth should be global and saved.
- static int EDRamWidth = 0x400;
- int last = EDRamWidth;
- EDRamWidth = new_size;
- return last;
+ return hleReportDebug(SCEGE, gpu->SetAddrTranslation(new_size));
}
const HLEFunction sceGe_user[] = {
@@ -629,9 +595,9 @@ const HLEFunction sceGe_user[] = {
{0XA4FC06A4, &WrapU_U, "sceGeSetCallback", 'i', "p" },
{0X05DB22CE, &WrapI_U, "sceGeUnsetCallback", 'i', "x" },
{0X1F6752AD, &WrapU_V, "sceGeEdramGetSize", 'x', "" },
- {0XB77905EA, &WrapU_I, "sceGeEdramSetAddrTranslation", 'x', "i" },
+ {0XB77905EA, &WrapU_U, "sceGeEdramSetAddrTranslation", 'x', "x" },
{0XDC93CFEF, &WrapU_I, "sceGeGetCmd", 'x', "i" },
- {0X57C8945B, &WrapI_IU, "sceGeGetMtx", 'i', "ix" },
+ {0X57C8945B, &WrapI_IU, "sceGeGetMtx", 'i', "ip" },
{0X438A385A, &WrapU_U, "sceGeSaveContext", 'x', "x" },
{0X0BF608FB, &WrapU_U, "sceGeRestoreContext", 'x', "x" },
{0X5FB86AB0, &WrapI_U, "sceGeListDeQueue", 'i', "x" },
diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp
index cb858c0e90..b9d1028b48 100644
--- a/Core/HLE/sceKernel.cpp
+++ b/Core/HLE/sceKernel.cpp
@@ -820,7 +820,7 @@ const HLEFunction ThreadManForUser[] =
{0X87D4DD36, &WrapI_IU, "sceKernelCancelReceiveMbx", 'i', "ix" },
{0XA8E8C846, &WrapI_IU, "sceKernelReferMbxStatus", 'i', "ip" },
- {0X7C0DC2A0, &WrapI_CIUUU, "sceKernelCreateMsgPipe", 'i', "sixxx" },
+ {0X7C0DC2A0, &WrapI_CIUUU, "sceKernelCreateMsgPipe", 'i', "sixxp" },
{0XF0B7DA1C, &WrapI_I, "sceKernelDeleteMsgPipe", 'i', "i" },
{0X876DBFAD, &WrapI_IUUUUU, "sceKernelSendMsgPipe", 'i', "ixxxxx" },
{0X7C41F2C2, &WrapI_IUUUUU, "sceKernelSendMsgPipeCB", 'i', "ixxxxx" },
@@ -831,7 +831,7 @@ const HLEFunction ThreadManForUser[] =
{0X349B864D, &WrapI_IUU, "sceKernelCancelMsgPipe", 'i', "ixx" },
{0X33BE4024, &WrapI_IU, "sceKernelReferMsgPipeStatus", 'i', "ip" },
- {0X56C039B5, &WrapI_CIUUU, "sceKernelCreateVpl", 'i', "sixxx" },
+ {0X56C039B5, &WrapI_CIUUU, "sceKernelCreateVpl", 'i', "sixxp" },
{0X89B3D48C, &WrapI_I, "sceKernelDeleteVpl", 'i', "i" },
{0XBED27435, &WrapI_IUUU, "sceKernelAllocateVpl", 'i', "ixxx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
{0XEC0A693F, &WrapI_IUUU, "sceKernelAllocateVplCB", 'i', "ixxx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
@@ -840,7 +840,7 @@ const HLEFunction ThreadManForUser[] =
{0X1D371B8A, &WrapI_IU, "sceKernelCancelVpl", 'i', "ix" },
{0X39810265, &WrapI_IU, "sceKernelReferVplStatus", 'i', "ip" },
- {0XC07BB470, &WrapI_CUUUUU, "sceKernelCreateFpl", 'i', "sxxxxx" },
+ {0XC07BB470, &WrapI_CUUUUU, "sceKernelCreateFpl", 'i', "sixxxp" },
{0XED1410E0, &WrapI_I, "sceKernelDeleteFpl", 'i', "i" },
{0XD979E9BF, &WrapI_IUU, "sceKernelAllocateFpl", 'i', "ixx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
{0XE7282CB6, &WrapI_IUU, "sceKernelAllocateFplCB", 'i', "ixx", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
@@ -864,7 +864,7 @@ const HLEFunction ThreadManForUser[] =
{0XD8B299AE, &WrapU_IUUU, "sceKernelSetVTimerHandler", 'x', "ixxx" },
{0X53B00E9A, &WrapU_IU64UU, "sceKernelSetVTimerHandlerWide", 'x', "iXxx" },
- {0X8DAFF657, &WrapI_CUUUUU, "sceKernelCreateTlspl", 'i', "sxxxxx" },
+ {0X8DAFF657, &WrapI_CUUUUU, "sceKernelCreateTlspl", 'i', "sixxxp" },
{0X32BF938E, &WrapI_I, "sceKernelDeleteTlspl", 'i', "i" },
{0X721067F3, &WrapI_IU, "sceKernelReferTlsplStatus", 'i', "xp" },
// Not completely certain about args.
@@ -908,7 +908,7 @@ const HLEFunction ThreadManForKernel[] =
{0x1fb15a32, &WrapU_IU, "sceKernelSetEventFlag", 'x', "ix", HLE_KERNEL_SYSCALL },
{0x812346e4, &WrapU_IU, "sceKernelClearEventFlag", 'x', "ix", HLE_KERNEL_SYSCALL },
{0x402fcf22, &WrapI_IUUUU, "sceKernelWaitEventFlag", 'i', "ixxpp", HLE_NOT_IN_INTERRUPT | HLE_KERNEL_SYSCALL},
- {0xc07bb470, &WrapI_CUUUUU, "sceKernelCreateFpl", 'i', "sxxxxx" ,HLE_KERNEL_SYSCALL },
+ {0xc07bb470, &WrapI_CUUUUU, "sceKernelCreateFpl", 'i', "sixxxp" ,HLE_KERNEL_SYSCALL },
{0xed1410e0, &WrapI_I, "sceKernelDeleteFpl", 'i', "i" ,HLE_KERNEL_SYSCALL },
{0x623ae665, &WrapI_IU, "sceKernelTryAllocateFpl", 'i', "ix" ,HLE_KERNEL_SYSCALL },
{0x616403ba, &WrapI_I, "sceKernelTerminateThread", 'i', "i" ,HLE_KERNEL_SYSCALL },
@@ -932,7 +932,7 @@ const HLEFunction ThreadManForKernel[] =
{0x0D81716A, &WrapI_IU, "sceKernelPollMbx", 'i', "ix", HLE_KERNEL_SYSCALL },
{0x87D4DD36, &WrapI_IU, "sceKernelCancelReceiveMbx", 'i', "ix", HLE_KERNEL_SYSCALL },
{0xA8E8C846, &WrapI_IU, "sceKernelReferMbxStatus", 'i', "ip", HLE_KERNEL_SYSCALL },
- {0x56C039B5, &WrapI_CIUUU, "sceKernelCreateVpl", 'i', "sixxx", HLE_KERNEL_SYSCALL },
+ {0x56C039B5, &WrapI_CIUUU, "sceKernelCreateVpl", 'i', "sixxp", HLE_KERNEL_SYSCALL },
{0x89B3D48C, &WrapI_I, "sceKernelDeleteVpl", 'i', "i", HLE_KERNEL_SYSCALL },
{0xBED27435, &WrapI_IUUU, "sceKernelAllocateVpl", 'i', "ixxx", HLE_KERNEL_SYSCALL | HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
{0xEC0A693F, &WrapI_IUUU, "sceKernelAllocateVplCB", 'i', "ixxx", HLE_KERNEL_SYSCALL | HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp
index ac853d5bb3..55b7ff7044 100644
--- a/Core/HLE/sceKernelMemory.cpp
+++ b/Core/HLE/sceKernelMemory.cpp
@@ -46,6 +46,7 @@ const int TLSPL_NUM_INDEXES = 16;
// STATE BEGIN
BlockAllocator userMemory(256);
BlockAllocator kernelMemory(256);
+BlockAllocator volatileMemory(256);
static int vplWaitTimer = -1;
static int fplWaitTimer = -1;
@@ -432,6 +433,7 @@ void __KernelMemoryInit()
MemBlockInfoInit();
kernelMemory.Init(PSP_GetKernelMemoryBase(), PSP_GetKernelMemoryEnd() - PSP_GetKernelMemoryBase(), false);
userMemory.Init(PSP_GetUserMemoryBase(), PSP_GetUserMemoryEnd() - PSP_GetUserMemoryBase(), false);
+ volatileMemory.Init(PSP_GetVolatileMemoryStart(), PSP_GetVolatileMemoryEnd() - PSP_GetVolatileMemoryStart(), false);
ParallelMemset(&g_threadManager, Memory::GetPointerWrite(PSP_GetKernelMemoryBase()), 0, PSP_GetUserMemoryEnd() - PSP_GetKernelMemoryBase());
NotifyMemInfo(MemBlockFlags::WRITE, PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd() - PSP_GetKernelMemoryBase(), "MemInit");
INFO_LOG(SCEKERNEL, "Kernel and user memory pools initialized");
@@ -457,12 +459,14 @@ void __KernelMemoryInit()
void __KernelMemoryDoState(PointerWrap &p)
{
- auto s = p.Section("sceKernelMemory", 1, 2);
+ auto s = p.Section("sceKernelMemory", 1, 3);
if (!s)
return;
kernelMemory.DoState(p);
userMemory.DoState(p);
+ if (s >= 3)
+ volatileMemory.DoState(p);
Do(p, vplWaitTimer);
CoreTiming::RestoreRegisterEvent(vplWaitTimer, "VplTimeout", __KernelVplTimeout);
@@ -481,6 +485,11 @@ void __KernelMemoryDoState(PointerWrap &p)
void __KernelMemoryShutdown()
{
+#ifdef _DEBUG
+ INFO_LOG(SCEKERNEL, "Shutting down volatile memory pool: ");
+ volatileMemory.ListBlocks();
+#endif
+ volatileMemory.Shutdown();
#ifdef _DEBUG
INFO_LOG(SCEKERNEL,"Shutting down user memory pool: ");
userMemory.ListBlocks();
@@ -495,6 +504,56 @@ void __KernelMemoryShutdown()
MemBlockInfoShutdown();
}
+BlockAllocator *BlockAllocatorFromID(int id) {
+ switch (id) {
+ case 1:
+ case 3:
+ case 4:
+ if (hleIsKernelMode())
+ return &kernelMemory;
+ return nullptr;
+
+ case 2:
+ case 6:
+ return &userMemory;
+
+ case 8:
+ case 10:
+ if (hleIsKernelMode())
+ return &userMemory;
+ return nullptr;
+
+ case 5:
+ return &volatileMemory;
+
+ default:
+ break;
+ }
+
+ return nullptr;
+}
+
+int BlockAllocatorToID(const BlockAllocator *alloc) {
+ if (alloc == &kernelMemory)
+ return 1;
+ if (alloc == &userMemory)
+ return 2;
+ if (alloc == &volatileMemory)
+ return 5;
+ return 0;
+}
+
+BlockAllocator *BlockAllocatorFromAddr(u32 addr) {
+ addr &= 0x3FFFFFFF;
+ if (Memory::IsKernelAndNotVolatileAddress(addr))
+ return &kernelMemory;
+ if (Memory::IsKernelAddress(addr))
+ return &volatileMemory;
+ if (Memory::IsRAMAddress(addr))
+ return &userMemory;
+ return nullptr;
+}
+
enum SceKernelFplAttr
{
PSP_FPL_ATTR_FIFO = 0x0000,
@@ -580,29 +639,18 @@ static void __KernelSortFplThreads(FPL *fpl)
std::stable_sort(fpl->waitingThreads.begin(), fpl->waitingThreads.end(), __FplThreadSortPriority);
}
-int sceKernelCreateFpl(const char *name, u32 mpid, u32 attr, u32 blockSize, u32 numBlocks, u32 optPtr)
-{
+int sceKernelCreateFpl(const char *name, u32 mpid, u32 attr, u32 blockSize, u32 numBlocks, u32 optPtr) {
if (!name)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid name", SCE_KERNEL_ERROR_NO_MEMORY);
- return SCE_KERNEL_ERROR_NO_MEMORY;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "invalid name");
if (mpid < 1 || mpid > 9 || mpid == 7)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, mpid);
- return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
- }
- // We only support user right now.
- if (mpid != 2 && mpid != 6)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_PERM, mpid);
- return SCE_KERNEL_ERROR_ILLEGAL_PERM;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", mpid);
+
+ BlockAllocator *allocator = BlockAllocatorFromID(mpid);
+ if (allocator == nullptr)
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_PERM, "invalid partition %d", mpid);
if (((attr & ~PSP_FPL_ATTR_KNOWN) & ~0xFF) != 0)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid attr parameter: %08x", SCE_KERNEL_ERROR_ILLEGAL_ATTR, attr);
- return SCE_KERNEL_ERROR_ILLEGAL_ATTR;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter: %08x", attr);
+
// There's probably a simpler way to get this same basic formula...
// This is based on results from a PSP.
bool illegalMemSize = blockSize == 0 || numBlocks == 0;
@@ -611,25 +659,16 @@ int sceKernelCreateFpl(const char *name, u32 mpid, u32 attr, u32 blockSize, u32
if (!illegalMemSize && (u64) numBlocks >= 0x100000000ULL / (((u64) blockSize + 3ULL) & ~3ULL))
illegalMemSize = true;
if (illegalMemSize)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid blockSize/count", SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE);
- return SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE;
- }
+ return hleReportWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE, "invalid blockSize/count");
int alignment = 4;
- if (optPtr != 0)
- {
- u32 size = Memory::Read_U32(optPtr);
- if (size > 8)
- WARN_LOG_REPORT(SCEKERNEL, "sceKernelCreateFpl(): unsupported extra options, size = %d", size);
+ if (Memory::IsValidRange(optPtr, 4)) {
+ u32 size = Memory::ReadUnchecked_U32(optPtr);
if (size >= 4)
alignment = Memory::Read_U32(optPtr + 4);
// Must be a power of 2 to be valid.
if ((alignment & (alignment - 1)) != 0)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateFpl(): invalid alignment %d", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, alignment);
- return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid alignment %d", alignment);
}
if (alignment < 4)
@@ -638,9 +677,8 @@ int sceKernelCreateFpl(const char *name, u32 mpid, u32 attr, u32 blockSize, u32
int alignedSize = ((int)blockSize + alignment - 1) & ~(alignment - 1);
u32 totalSize = alignedSize * numBlocks;
bool atEnd = (attr & PSP_FPL_ATTR_HIGHMEM) != 0;
- u32 address = userMemory.Alloc(totalSize, atEnd, "FPL");
- if (address == (u32)-1)
- {
+ u32 address = allocator->Alloc(totalSize, atEnd, "FPL");
+ if (address == (u32)-1) {
DEBUG_LOG(SCEKERNEL, "sceKernelCreateFpl(\"%s\", partition=%i, attr=%08x, bsize=%i, nb=%i) FAILED - out of ram",
name, mpid, attr, blockSize, numBlocks);
return SCE_KERNEL_ERROR_NO_MEMORY;
@@ -682,7 +720,10 @@ int sceKernelDeleteFpl(SceUID uid)
if (wokeThreads)
hleReSchedule("fpl deleted");
- userMemory.Free(fpl->address);
+ BlockAllocator *alloc = BlockAllocatorFromAddr(fpl->address);
+ _assert_msg_(alloc != nullptr, "Should always have a valid allocator/address");
+ if (alloc)
+ alloc->Free(fpl->address);
return kernelObjects.Destroy(uid);
}
else
@@ -955,18 +996,23 @@ public:
alloc->Free(address);
}
bool IsValid() {return address != (u32)-1;}
- BlockAllocator *alloc;
void DoState(PointerWrap &p) override
{
- auto s = p.Section("PMB", 1);
+ auto s = p.Section("PMB", 1, 2);
if (!s)
return;
Do(p, address);
DoArray(p, name, sizeof(name));
+ if (s >= 2) {
+ int allocType = BlockAllocatorToID(alloc);
+ Do(p, allocType);
+ alloc = BlockAllocatorFromID(allocType);
+ }
}
+ BlockAllocator *alloc;
u32 address;
char name[32];
};
@@ -986,44 +1032,28 @@ static u32 sceKernelTotalFreeMemSize()
return retVal;
}
-int sceKernelAllocPartitionMemory(int partition, const char *name, int type, u32 size, u32 addr)
-{
- if (name == NULL)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid name", SCE_KERNEL_ERROR_ERROR);
- return SCE_KERNEL_ERROR_ERROR;
- }
- if (size == 0)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid size %x", SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED, size);
- return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
+int sceKernelAllocPartitionMemory(int partition, const char *name, int type, u32 size, u32 addr) {
+ if (type < PSP_SMEM_Low || type > PSP_SMEM_HighAligned)
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_MEMBLOCKTYPE, "invalid type %x", type);
+ // Alignment is only allowed for powers of 2.
+ if (type == PSP_SMEM_LowAligned || type == PSP_SMEM_HighAligned) {
+ if ((addr & (addr - 1)) != 0 || addr == 0)
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ALIGNMENT_SIZE, "invalid alignment %x", addr);
}
if (partition < 1 || partition > 9 || partition == 7)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid partition %x", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, partition);
- return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
- }
- // We only support user right now.
- if (partition != 2 && partition != 5 && partition != 6)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid partition %x", SCE_KERNEL_ERROR_ILLEGAL_PARTITION, partition);
- return SCE_KERNEL_ERROR_ILLEGAL_PARTITION;
- }
- if (type < PSP_SMEM_Low || type > PSP_SMEM_HighAligned)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid type %x", SCE_KERNEL_ERROR_ILLEGAL_MEMBLOCKTYPE, type);
- return SCE_KERNEL_ERROR_ILLEGAL_MEMBLOCKTYPE;
- }
- // Alignment is only allowed for powers of 2.
- if ((type == PSP_SMEM_LowAligned || type == PSP_SMEM_HighAligned) && ((addr & (addr - 1)) != 0 || addr == 0))
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelAllocPartitionMemory(): invalid alignment %x", SCE_KERNEL_ERROR_ILLEGAL_ALIGNMENT_SIZE, addr);
- return SCE_KERNEL_ERROR_ILLEGAL_ALIGNMENT_SIZE;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %x", partition);
- PartitionMemoryBlock *block = new PartitionMemoryBlock(&userMemory, name, size, (MemblockType)type, addr);
- if (!block->IsValid())
- {
+ BlockAllocator *allocator = BlockAllocatorFromID(partition);
+ if (allocator == nullptr)
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_PARTITION, "invalid partition %x", partition);
+
+ if (name == nullptr)
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ERROR, "invalid name");
+ if (size == 0)
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED, "invalid size %x", size);
+
+ PartitionMemoryBlock *block = new PartitionMemoryBlock(allocator, name, size, (MemblockType)type, addr);
+ if (!block->IsValid()) {
delete block;
ERROR_LOG(SCEKERNEL, "sceKernelAllocPartitionMemory(partition = %i, %s, type= %i, size= %i, addr= %08x): allocation failed", partition, name, type, size, addr);
return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED;
@@ -1451,40 +1481,23 @@ static void __KernelSortVplThreads(VPL *vpl)
std::stable_sort(vpl->waitingThreads.begin(), vpl->waitingThreads.end(), __VplThreadSortPriority);
}
-SceUID sceKernelCreateVpl(const char *name, int partition, u32 attr, u32 vplSize, u32 optPtr)
-{
+SceUID sceKernelCreateVpl(const char *name, int partition, u32 attr, u32 vplSize, u32 optPtr) {
if (!name)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): invalid name", SCE_KERNEL_ERROR_ERROR);
- return SCE_KERNEL_ERROR_ERROR;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ERROR, "invalid name");
if (partition < 1 || partition > 9 || partition == 7)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, partition);
- return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
- }
- // We only support user right now.
- if (partition != 2 && partition != 6)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_PERM, partition);
- return SCE_KERNEL_ERROR_ILLEGAL_PERM;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", partition);
+
+ BlockAllocator *allocator = BlockAllocatorFromID(partition);
+ if (allocator == nullptr)
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_PERM, "invalid partition %d", partition);
+
if (((attr & ~PSP_VPL_ATTR_KNOWN) & ~0xFF) != 0)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): invalid attr parameter: %08x", SCE_KERNEL_ERROR_ILLEGAL_ATTR, attr);
- return SCE_KERNEL_ERROR_ILLEGAL_ATTR;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter: %08x", attr);
if (vplSize == 0)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): invalid size", SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE);
- return SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE, "invalid size");
// Block Allocator seems to A-OK this, let's stop it here.
if (vplSize >= 0x80000000)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateVpl(): way too big size", SCE_KERNEL_ERROR_NO_MEMORY);
- return SCE_KERNEL_ERROR_NO_MEMORY;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "way too big size");
// Can't have that little space in a Vpl, sorry.
if (vplSize <= 0x30)
@@ -1493,12 +1506,9 @@ SceUID sceKernelCreateVpl(const char *name, int partition, u32 attr, u32 vplSize
// We ignore the upalign to 256 and do it ourselves by 8.
u32 allocSize = vplSize;
- u32 memBlockPtr = userMemory.Alloc(allocSize, (attr & PSP_VPL_ATTR_HIGHMEM) != 0, "VPL");
+ u32 memBlockPtr = allocator->Alloc(allocSize, (attr & PSP_VPL_ATTR_HIGHMEM) != 0, "VPL");
if (memBlockPtr == (u32)-1)
- {
- ERROR_LOG(SCEKERNEL, "sceKernelCreateVpl(): Failed to allocate %i bytes of pool data", vplSize);
- return SCE_KERNEL_ERROR_NO_MEMORY;
- }
+ return hleLogError(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "failed to allocate %i bytes of pool data", vplSize);
VPL *vpl = new VPL;
SceUID id = kernelObjects.Create(vpl);
@@ -1542,7 +1552,10 @@ int sceKernelDeleteVpl(SceUID uid)
if (wokeThreads)
hleReSchedule("vpl deleted");
- userMemory.Free(vpl->address);
+ BlockAllocator *alloc = BlockAllocatorFromAddr(vpl->address);
+ _assert_msg_(alloc != nullptr, "Should always have a valid allocator/address");
+ if (alloc)
+ alloc->Free(vpl->address);
kernelObjects.Destroy(uid);
return 0;
}
@@ -1994,7 +2007,7 @@ int __KernelFreeTls(TLSPL *tls, SceUID threadID)
__KernelResumeThreadFromWait(waitingThreadID, freedAddress);
// Gotta watch the thread to quit as well, since they've allocated now.
- tlsplThreadEndChecks.insert(std::make_pair(waitingThreadID, uid));
+ tlsplThreadEndChecks.emplace(waitingThreadID, uid);
// No need to continue or free it, we're done.
return 0;
@@ -2044,29 +2057,17 @@ void __KernelTlsplThreadEnd(SceUID threadID)
tlsplThreadEndChecks.erase(locked.first, locked.second);
}
-SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 blockSize, u32 count, u32 optionsPtr)
-{
+SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 blockSize, u32 count, u32 optionsPtr) {
if (!name)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): invalid name", SCE_KERNEL_ERROR_NO_MEMORY);
- return SCE_KERNEL_ERROR_NO_MEMORY;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "invalid name");
if ((attr & ~PSP_TLSPL_ATTR_KNOWN) >= 0x100)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): invalid attr parameter: %08x", SCE_KERNEL_ERROR_ILLEGAL_ATTR, attr);
- return SCE_KERNEL_ERROR_ILLEGAL_ATTR;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter: %08x", attr);
if (partition < 1 || partition > 9 || partition == 7)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, partition);
- return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
- }
- // We only support user right now.
- if (partition != 2 && partition != 6)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_PERM, partition);
- return SCE_KERNEL_ERROR_ILLEGAL_PERM;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", partition);
+
+ BlockAllocator *allocator = BlockAllocatorFromID(partition);
+ if (allocator == nullptr)
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_PERM, "invalid partition %x", partition);
// There's probably a simpler way to get this same basic formula...
// This is based on results from a PSP.
@@ -2076,41 +2077,29 @@ SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 block
if (!illegalMemSize && (u64) count >= 0x100000000ULL / (((u64) blockSize + 3ULL) & ~3ULL))
illegalMemSize = true;
if (illegalMemSize)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): invalid blockSize/count", SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE);
- return SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_MEMSIZE, "invalid blockSize/count");
int index = -1;
- for (int i = 0; i < TLSPL_NUM_INDEXES; ++i)
- if (tlsplUsedIndexes[i] == false)
- {
+ for (int i = 0; i < TLSPL_NUM_INDEXES; ++i) {
+ if (tlsplUsedIndexes[i] == false) {
index = i;
break;
}
+ }
if (index == -1)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateTlspl(): ran out of indexes for TLS pools", PSP_ERROR_TOO_MANY_TLSPL);
- return PSP_ERROR_TOO_MANY_TLSPL;
- }
+ return hleLogWarning(SCEKERNEL, PSP_ERROR_TOO_MANY_TLSPL, "ran out of indexes for TLS pools");
// Unless otherwise specified, we align to 4 bytes (a mips word.)
u32 alignment = 4;
- if (optionsPtr != 0)
- {
- u32 size = Memory::Read_U32(optionsPtr);
- if (size > 8)
- WARN_LOG_REPORT(SCEKERNEL, "sceKernelCreateTlspl(%s) unsupported options parameter, size = %d", name, size);
+ if (Memory::IsValidRange(optionsPtr, 4)) {
+ u32 size = Memory::ReadUnchecked_U32(optionsPtr);
if (size >= 8)
alignment = Memory::Read_U32(optionsPtr + 4);
// Note that 0 intentionally is allowed.
if ((alignment & (alignment - 1)) != 0)
- {
- ERROR_LOG_REPORT(SCEKERNEL, "sceKernelCreateTlspl(%s): alignment is not a power of 2: %d", name, alignment);
- return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
- }
+ return hleLogError(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "alignment is not a power of 2: %d", alignment);
// This goes for 0, 1, and 2. Can't have less than 4 byte alignment.
if (alignment < 4)
alignment = 4;
@@ -2120,16 +2109,13 @@ SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 block
u32 alignedSize = (blockSize + alignment - 1) & ~(alignment - 1);
u32 totalSize = alignedSize * count;
- u32 blockPtr = userMemory.Alloc(totalSize, (attr & PSP_TLSPL_ATTR_HIGHMEM) != 0, name);
+ u32 blockPtr = allocator->Alloc(totalSize, (attr & PSP_TLSPL_ATTR_HIGHMEM) != 0, name);
#ifdef _DEBUG
- userMemory.ListBlocks();
+ allocator->ListBlocks();
#endif
- if (blockPtr == (u32) -1)
- {
- ERROR_LOG(SCEKERNEL, "%08x=sceKernelCreateTlspl(%s, %d, %08x, %d, %d, %08x): failed to allocate memory", SCE_KERNEL_ERROR_NO_MEMORY, name, partition, attr, blockSize, count, optionsPtr);
- return SCE_KERNEL_ERROR_NO_MEMORY;
- }
+ if (blockPtr == (u32)-1)
+ return hleLogError(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "failed to allocate memory");
TLSPL *tls = new TLSPL();
SceUID id = kernelObjects.Create(tls);
@@ -2148,9 +2134,7 @@ SceUID sceKernelCreateTlspl(const char *name, u32 partition, u32 attr, u32 block
tls->alignment = alignment;
tls->usage.resize(count, 0);
- WARN_LOG(SCEKERNEL, "%08x=sceKernelCreateTlspl(%s, %d, %08x, %d, %d, %08x)", id, name, partition, attr, blockSize, count, optionsPtr);
-
- return id;
+ return hleLogSuccessInfoI(SCEKERNEL, id);
}
int sceKernelDeleteTlspl(SceUID uid)
@@ -2178,7 +2162,10 @@ int sceKernelDeleteTlspl(SceUID uid)
HLEKernel::ResumeFromWait(threadID, WAITTYPE_TLSPL, uid, 0);
hleReSchedule("deleted tlspl");
- userMemory.Free(tls->address);
+ BlockAllocator *allocator = BlockAllocatorFromAddr(tls->address);
+ _assert_msg_(allocator != nullptr, "Should always have a valid allocator/address");
+ if (allocator)
+ allocator->Free(tls->address);
tlsplUsedIndexes[tls->ntls.index] = false;
kernelObjects.Destroy(uid);
}
@@ -2187,68 +2174,89 @@ int sceKernelDeleteTlspl(SceUID uid)
return error;
}
-int sceKernelGetTlsAddr(SceUID uid)
-{
- // TODO: Allocate downward if PSP_TLSPL_ATTR_HIGHMEM?
- DEBUG_LOG(SCEKERNEL, "sceKernelGetTlsAddr(%08x)", uid);
+struct FindTLSByIndexArg {
+ int index;
+ TLSPL *result = nullptr;
+};
+static bool FindTLSByIndex(TLSPL *possible, FindTLSByIndexArg *state) {
+ if (possible->ntls.index == state->index) {
+ state->result = possible;
+ return false;
+ }
+ return true;
+}
+
+int sceKernelGetTlsAddr(SceUID uid) {
if (!__KernelIsDispatchEnabled() || __IsInInterrupt())
- return 0;
+ return hleLogWarning(SCEKERNEL, 0, "dispatch disabled");
u32 error;
TLSPL *tls = kernelObjects.Get(uid, error);
- if (tls)
- {
- SceUID threadID = __KernelGetCurThread();
- int allocBlock = -1;
- bool needsClear = false;
+ if (!tls) {
+ if (uid < 0)
+ return hleLogError(SCEKERNEL, 0, "tlspl not found");
- // If the thread already has one, return it.
+ // There's this weird behavior where it looks up by index. Maybe we shouldn't use uids...
+ if (!tlsplUsedIndexes[(uid >> 3) & 15])
+ return hleLogError(SCEKERNEL, 0, "tlspl not found");
+
+ FindTLSByIndexArg state;
+ state.index = (uid >> 3) & 15;
+ kernelObjects.Iterate(&FindTLSByIndex, &state);
+ if (!state.result)
+ return hleLogError(SCEKERNEL, 0, "tlspl not found");
+
+ tls = state.result;
+ }
+
+ SceUID threadID = __KernelGetCurThread();
+ int allocBlock = -1;
+ bool needsClear = false;
+
+ // If the thread already has one, return it.
+ for (size_t i = 0; i < tls->ntls.totalBlocks && allocBlock == -1; ++i)
+ {
+ if (tls->usage[i] == threadID)
+ allocBlock = (int) i;
+ }
+
+ if (allocBlock == -1)
+ {
for (size_t i = 0; i < tls->ntls.totalBlocks && allocBlock == -1; ++i)
{
- if (tls->usage[i] == threadID)
- allocBlock = (int) i;
+ // The PSP doesn't give the same block out twice in a row, even if freed.
+ if (tls->usage[tls->next] == 0)
+ allocBlock = tls->next;
+ tls->next = (tls->next + 1) % tls->ntls.totalBlocks;
}
- if (allocBlock == -1)
+ if (allocBlock != -1)
{
- for (size_t i = 0; i < tls->ntls.totalBlocks && allocBlock == -1; ++i)
- {
- // The PSP doesn't give the same block out twice in a row, even if freed.
- if (tls->usage[tls->next] == 0)
- allocBlock = tls->next;
- tls->next = (tls->next + 1) % tls->ntls.totalBlocks;
- }
-
- if (allocBlock != -1)
- {
- tls->usage[allocBlock] = threadID;
- tlsplThreadEndChecks.insert(std::make_pair(threadID, uid));
- --tls->ntls.freeBlocks;
- needsClear = true;
- }
+ tls->usage[allocBlock] = threadID;
+ tlsplThreadEndChecks.emplace(threadID, uid);
+ --tls->ntls.freeBlocks;
+ needsClear = true;
}
-
- if (allocBlock == -1)
- {
- tls->waitingThreads.push_back(threadID);
- __KernelWaitCurThread(WAITTYPE_TLSPL, uid, 1, 0, false, "allocate tls");
- return 0;
- }
-
- u32 alignedSize = (tls->ntls.blockSize + tls->alignment - 1) & ~(tls->alignment - 1);
- u32 allocAddress = tls->address + allocBlock * alignedSize;
- NotifyMemInfo(MemBlockFlags::SUB_ALLOC, allocAddress, tls->ntls.blockSize, "TlsAddr");
-
- // We clear the blocks upon first allocation (and also when they are freed, both are necessary.)
- if (needsClear) {
- Memory::Memset(allocAddress, 0, tls->ntls.blockSize, "TlsAddr");
- }
-
- return allocAddress;
}
- else
- return 0;
+
+ if (allocBlock == -1)
+ {
+ tls->waitingThreads.push_back(threadID);
+ __KernelWaitCurThread(WAITTYPE_TLSPL, uid, 1, 0, false, "allocate tls");
+ return hleLogDebug(SCEKERNEL, 0, "waiting for tls alloc");
+ }
+
+ u32 alignedSize = (tls->ntls.blockSize + tls->alignment - 1) & ~(tls->alignment - 1);
+ u32 allocAddress = tls->address + allocBlock * alignedSize;
+ NotifyMemInfo(MemBlockFlags::SUB_ALLOC, allocAddress, tls->ntls.blockSize, "TlsAddr");
+
+ // We clear the blocks upon first allocation (and also when they are freed, both are necessary.)
+ if (needsClear) {
+ Memory::Memset(allocAddress, 0, tls->ntls.blockSize, "TlsAddr");
+ }
+
+ return hleLogDebug(SCEKERNEL, allocAddress);
}
// Parameters are an educated guess.
diff --git a/Core/HLE/sceKernelMemory.h b/Core/HLE/sceKernelMemory.h
index 29d7356d00..b217974170 100644
--- a/Core/HLE/sceKernelMemory.h
+++ b/Core/HLE/sceKernelMemory.h
@@ -40,6 +40,10 @@ KernelObject *__KernelMemoryVPLObject();
KernelObject *__KernelMemoryPMBObject();
KernelObject *__KernelTlsplObject();
+BlockAllocator *BlockAllocatorFromID(int id);
+int BlockAllocatorToID(const BlockAllocator *alloc);
+BlockAllocator *BlockAllocatorFromAddr(u32 addr);
+
SceUID sceKernelCreateVpl(const char *name, int partition, u32 attr, u32 vplSize, u32 optPtr);
int sceKernelDeleteVpl(SceUID uid);
int sceKernelAllocateVpl(SceUID uid, u32 size, u32 addrPtr, u32 timeoutPtr);
diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp
index a32604e928..4f3d586672 100644
--- a/Core/HLE/sceKernelModule.cpp
+++ b/Core/HLE/sceKernelModule.cpp
@@ -1144,6 +1144,7 @@ static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, u8 *InBuffer) {
}
static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAddress, bool fromTop, std::string *error_string, u32 *magic, u32 &error) {
+ u32 crc = crc32(0, ptr, (uInt)elfSize);
PSPModule *module = new PSPModule();
kernelObjects.Create(module);
loadedModules.insert(module->GetUID());
@@ -1170,14 +1171,14 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
if (IsHLEVersionedModule(head->modname)) {
int ver = (head->module_ver_hi << 8) | head->module_ver_lo;
- INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x", head->modname, ver, head->devkitversion);
+ INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x, crc %x", head->modname, ver, head->devkitversion, crc);
reportedModule = true;
if (!strcmp(head->modname, "sceMpeg_library")) {
- __MpegLoadModule(ver);
+ __MpegLoadModule(ver, crc);
}
if (!strcmp(head->modname, "scePsmfP_library") || !strcmp(head->modname, "scePsmfPlayer")) {
- __PsmfPlayerLoadModule(head->devkitversion);
+ __PsmfPlayerLoadModule(head->devkitversion, crc);
}
}
@@ -1610,10 +1611,10 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
INFO_LOG(SCEMODULE, "Loading module %s with version %04x, devkit %08x", modinfo->name, modinfo->moduleVersion, devkitVersion);
if (!strcmp(modinfo->name, "sceMpeg_library")) {
- __MpegLoadModule(modinfo->moduleVersion);
+ __MpegLoadModule(modinfo->moduleVersion, crc);
}
if (!strcmp(modinfo->name, "scePsmfP_library") || !strcmp(modinfo->name, "scePsmfPlayer")) {
- __PsmfPlayerLoadModule(devkitVersion);
+ __PsmfPlayerLoadModule(devkitVersion, crc);
}
}
diff --git a/Core/HLE/sceKernelMsgPipe.cpp b/Core/HLE/sceKernelMsgPipe.cpp
index b16e8060e2..0ff34c6290 100644
--- a/Core/HLE/sceKernelMsgPipe.cpp
+++ b/Core/HLE/sceKernelMsgPipe.cpp
@@ -140,10 +140,13 @@ struct MsgPipe : public KernelObject
int GetIDType() const override { return SCE_KERNEL_TMID_Mpipe; }
MsgPipe() : buffer(0) {}
- ~MsgPipe()
- {
- if (buffer != 0)
- userMemory.Free(buffer);
+ ~MsgPipe() {
+ if (buffer != 0) {
+ BlockAllocator *alloc = BlockAllocatorFromAddr(buffer);
+ _assert_msg_(alloc != nullptr, "Should always have a valid allocator/address");
+ if (alloc)
+ alloc->Free(buffer);
+ }
}
u32 GetUsedSize()
@@ -667,41 +670,26 @@ void __KernelMsgPipeDoState(PointerWrap &p)
CoreTiming::RestoreRegisterEvent(waitTimer, "MsgPipeTimeout", __KernelMsgPipeTimeout);
}
-int sceKernelCreateMsgPipe(const char *name, int partition, u32 attr, u32 size, u32 optionsPtr)
-{
+int sceKernelCreateMsgPipe(const char *name, int partition, u32 attr, u32 size, u32 optionsPtr) {
if (!name)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateMsgPipe(): invalid name", SCE_KERNEL_ERROR_NO_MEMORY);
- return SCE_KERNEL_ERROR_NO_MEMORY;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "invalid name");
if (partition < 1 || partition > 9 || partition == 7)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateMsgPipe(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, partition);
- return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
- }
- // We only support user right now.
- if (partition != 2 && partition != 6)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateMsgPipe(): invalid partition %d", SCE_KERNEL_ERROR_ILLEGAL_PERM, partition);
- return SCE_KERNEL_ERROR_ILLEGAL_PERM;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", partition);
+
+ BlockAllocator *allocator = BlockAllocatorFromID(partition);
+ if (allocator == nullptr)
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_PERM, "invalid partition %d", partition);
+
if ((attr & ~SCE_KERNEL_MPA_KNOWN) >= 0x100)
- {
- WARN_LOG_REPORT(SCEKERNEL, "%08x=sceKernelCreateEventFlag(%s): invalid attr parameter: %08x", SCE_KERNEL_ERROR_ILLEGAL_ATTR, name, attr);
- return SCE_KERNEL_ERROR_ILLEGAL_ATTR;
- }
+ return hleLogWarning(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_ATTR, "invalid attr parameter: %08x", attr);
u32 memBlockPtr = 0;
- if (size != 0)
- {
+ if (size != 0) {
// We ignore the upalign to 256.
u32 allocSize = size;
- memBlockPtr = userMemory.Alloc(allocSize, (attr & SCE_KERNEL_MPA_HIGHMEM) != 0, "MsgPipe");
+ memBlockPtr = allocator->Alloc(allocSize, (attr & SCE_KERNEL_MPA_HIGHMEM) != 0, "MsgPipe");
if (memBlockPtr == (u32)-1)
- {
- ERROR_LOG(SCEKERNEL, "%08x=sceKernelCreateEventFlag(%s): Failed to allocate %i bytes for buffer", SCE_KERNEL_ERROR_NO_MEMORY, name, size);
- return SCE_KERNEL_ERROR_NO_MEMORY;
- }
+ return hleLogError(SCEKERNEL, SCE_KERNEL_ERROR_NO_MEMORY, "failed to allocate %i bytes for buffer", size);
}
MsgPipe *m = new MsgPipe();
diff --git a/Core/HLE/sceKernelMutex.cpp b/Core/HLE/sceKernelMutex.cpp
index 2cba7b5ca5..6921ffdf26 100644
--- a/Core/HLE/sceKernelMutex.cpp
+++ b/Core/HLE/sceKernelMutex.cpp
@@ -213,7 +213,7 @@ static void __KernelMutexAcquireLock(PSPMutex *mutex, int count, SceUID thread)
_dbg_assert_msg_((*iter).second != mutex->GetUID(), "Thread %d / mutex %d wasn't removed from mutexHeldLocks properly.", thread, mutex->GetUID());
#endif
- mutexHeldLocks.insert(std::make_pair(thread, mutex->GetUID()));
+ mutexHeldLocks.emplace(thread, mutex->GetUID());
mutex->nm.lockLevel = count;
mutex->nm.lockThread = thread;
diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp
index ea9caaba1e..f717cd5a3e 100644
--- a/Core/HLE/sceKernelThread.cpp
+++ b/Core/HLE/sceKernelThread.cpp
@@ -230,7 +230,7 @@ public:
MipsCallManager() : idGen_(0) {}
u32 add(MipsCall *call) {
u32 id = genId();
- calls_.insert(std::pair(id, call));
+ calls_.emplace(id, call);
return id;
}
MipsCall *get(u32 id) {
diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp
index 8aa429ba1d..929e782d0c 100644
--- a/Core/HLE/sceMpeg.cpp
+++ b/Core/HLE/sceMpeg.cpp
@@ -125,7 +125,7 @@ struct SceMpegLLI
};
void SceMpegAu::read(u32 addr) {
- Memory::Memcpy(this, addr, sizeof(this), "SceMpegAu");
+ Memory::Memcpy(this, addr, sizeof(*this), "SceMpegAu");
pts = (pts & 0xFFFFFFFFULL) << 32 | (((u64)pts) >> 32);
dts = (dts & 0xFFFFFFFFULL) << 32 | (((u64)dts) >> 32);
}
@@ -133,7 +133,7 @@ void SceMpegAu::read(u32 addr) {
void SceMpegAu::write(u32 addr) {
pts = (pts & 0xFFFFFFFFULL) << 32 | (((u64)pts) >> 32);
dts = (dts & 0xFFFFFFFFULL) << 32 | (((u64)dts) >> 32);
- Memory::Memcpy(addr, this, sizeof(this), "SceMpegAu");
+ Memory::Memcpy(addr, this, sizeof(*this), "SceMpegAu");
}
/*
@@ -258,7 +258,8 @@ struct MpegContext {
};
static bool isMpegInit;
-static int mpegLibVersion;
+static int mpegLibVersion = 0;
+static u32 mpegLibCrc = 0;
static u32 streamIdGen;
static int actionPostPut;
static std::map mpegMap;
@@ -403,7 +404,7 @@ void __MpegInit() {
}
void __MpegDoState(PointerWrap &p) {
- auto s = p.Section("sceMpeg", 1, 3);
+ auto s = p.Section("sceMpeg", 1, 4);
if (!s)
return;
@@ -421,7 +422,14 @@ void __MpegDoState(PointerWrap &p) {
ringbufferPutPacketsAdded = 0;
} else {
Do(p, ringbufferPutPacketsAdded);
+ }
+ if (s < 4) {
+ mpegLibCrc = 0;
}
+ else {
+ Do(p, mpegLibCrc);
+ }
+
Do(p, streamIdGen);
Do(p, mpegLibVersion);
}
@@ -440,8 +448,9 @@ void __MpegShutdown() {
mpegMap.clear();
}
-void __MpegLoadModule(int version) {
+void __MpegLoadModule(int version,u32 crc) {
mpegLibVersion = version;
+ mpegLibCrc = crc;
}
static u32 sceMpegInit() {
@@ -450,7 +459,7 @@ static u32 sceMpegInit() {
// TODO: Need to properly hook module load/unload for this to work right.
//return ERROR_MPEG_ALREADY_INIT;
} else {
- INFO_LOG(ME, "sceMpegInit()");
+ INFO_LOG(ME, "sceMpegInit(), mpegLibVersion 0x%0x, mpegLibcrc %x", mpegLibVersion, mpegLibCrc);
}
isMpegInit = true;
return hleDelayResult(0, "mpeg init", 750);
@@ -1361,8 +1370,14 @@ static int sceMpegAvcDecodeYCbCr(u32 mpeg, u32 auAddr, u32 bufferAddr, u32 initA
// Flush structs back to memory
avcAu.write(auAddr);
- // Save the current frame's status to initAddr
- Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr);
+ if (mpegLibVersion >= 0x010A) {
+ // Sunday Vs Magazine Shuuketsu! Choujou Daikessen expect, issue #11060
+ Memory::Write_U32(1, initAddr);
+ }
+ else {
+ // Save the current frame's status to initAddr
+ Memory::Write_U32(ctx->avc.avcFrameStatus, initAddr);
+ }
ctx->avc.avcDecodeResult = MPEG_AVC_DECODE_SUCCESS;
DEBUG_LOG(ME, "sceMpegAvcDecodeYCbCr(%08x, %08x, %08x, %08x)", mpeg, auAddr, bufferAddr, initAddr);
diff --git a/Core/HLE/sceMpeg.h b/Core/HLE/sceMpeg.h
index aded1238cc..e8bde2da34 100644
--- a/Core/HLE/sceMpeg.h
+++ b/Core/HLE/sceMpeg.h
@@ -79,7 +79,7 @@ void __MpegInit();
void __MpegDoState(PointerWrap &p);
void __MpegShutdown();
-void __MpegLoadModule(int version);
+void __MpegLoadModule(int version, u32 crc);
void Register_sceMpeg();
diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp
index c2a8987408..13b87f6b6b 100644
--- a/Core/HLE/sceNet.cpp
+++ b/Core/HLE/sceNet.cpp
@@ -691,7 +691,7 @@ static u32 sceWlanGetEtherAddr(u32 addrAddr) {
addr[0] &= 0xfc;
} else {
// Read MAC Address from config
- if (!ParseMacAddress(g_Config.sMACAddress.c_str(), addr)) {
+ if (!ParseMacAddress(g_Config.sMACAddress, addr)) {
ERROR_LOG(SCENET, "Error parsing mac address %s", g_Config.sMACAddress.c_str());
Memory::Memset(addrAddr, 0, 6);
}
diff --git a/Core/HLE/scePsmf.cpp b/Core/HLE/scePsmf.cpp
index b2aefa9a1a..06fc172c53 100644
--- a/Core/HLE/scePsmf.cpp
+++ b/Core/HLE/scePsmf.cpp
@@ -59,6 +59,7 @@ static const int audioSamplesBytes = audioSamples * 4;
static int videoPixelMode = GE_CMODE_32BIT_ABGR8888;
static int videoLoopStatus = PSMF_PLAYER_CONFIG_NO_LOOP;
static int psmfPlayerLibVersion = 0;
+static u32 psmfPlayerLibcrc = 0;
int eventPsmfPlayerStatusChange = -1;
@@ -175,35 +176,35 @@ public:
int FindEPWithTimestamp(int pts) const;
- u32 magic;
- u32 version;
- u32 streamOffset;
- u32 streamSize;
- u32 headerSize;
- u32 headerOffset;
- u32 streamType;
- u32 streamChannel;
+ u32 magic = 0;
+ u32 version = 0;
+ u32 streamOffset = 0;
+ u32 streamSize = 0;
+ u32 headerSize = 0;
+ u32 headerOffset = 0;
+ u32 streamType = 0;
+ u32 streamChannel = 0;
// 0x50
- u32 streamDataTotalSize;
- u32 presentationStartTime;
- u32 presentationEndTime;
- u32 streamDataNextBlockSize;
- u32 streamDataNextInnerBlockSize;
+ u32 streamDataTotalSize = 0;
+ u32 presentationStartTime = 0;
+ u32 presentationEndTime = 0;
+ u32 streamDataNextBlockSize = 0;
+ u32 streamDataNextInnerBlockSize = 0;
- int numStreams;
- int currentStreamNum;
- int currentStreamType;
- int currentStreamChannel;
+ int numStreams = 0;
+ int currentStreamNum = 0;
+ int currentStreamType = 0;
+ int currentStreamChannel = 0;
// parameters gotten from streams
// I guess this is the seek information?
- u32 EPMapOffset;
- u32 EPMapEntriesNum;
+ u32 EPMapOffset = 0;
+ u32 EPMapEntriesNum = 0;
// These shouldn't be here, just here for convenience with old states.
- int videoWidth;
- int videoHeight;
- int audioChannels;
- int audioFrequency;
+ int videoWidth = 0;
+ int videoHeight = 0;
+ int audioChannels = 0;
+ int audioFrequency = 0;
std::vector EPMap;
PsmfStreamMap streamMap;
@@ -696,8 +697,9 @@ void __PsmfInit() {
eventPsmfPlayerStatusChange = CoreTiming::RegisterEvent("PsmfPlayerStatusChange", &__PsmfPlayerStatusChange);
}
-void __PsmfPlayerLoadModule(int devkitVersion) {
+void __PsmfPlayerLoadModule(int devkitVersion, u32 crc) {
psmfPlayerLibVersion = devkitVersion;
+ psmfPlayerLibcrc = crc;
}
void __PsmfDoState(PointerWrap &p) {
@@ -709,7 +711,7 @@ void __PsmfDoState(PointerWrap &p) {
}
void __PsmfPlayerDoState(PointerWrap &p) {
- auto s = p.Section("scePsmfPlayer", 1, 3);
+ auto s = p.Section("scePsmfPlayer", 1, 4);
if (!s)
return;
@@ -722,6 +724,11 @@ void __PsmfPlayerDoState(PointerWrap &p) {
Do(p, eventPsmfPlayerStatusChange);
}
CoreTiming::RestoreRegisterEvent(eventPsmfPlayerStatusChange, "PsmfPlayerStatusChangeEvent", &__PsmfPlayerStatusChange);
+ if (s < 4) {
+ psmfPlayerLibcrc = 0;
+ } else {
+ Do(p, psmfPlayerLibcrc);
+ }
if (s < 2) {
// Assume the latest, which is what we were emulating before.
psmfPlayerLibVersion = 0x06060010;
@@ -1157,7 +1164,8 @@ static int scePsmfPlayerCreate(u32 psmfPlayer, u32 dataPtr) {
int delayUs = 20000;
DelayPsmfStateChange(psmfPlayer, PSMF_PLAYER_STATUS_INIT, delayUs);
- return hleLogSuccessInfoI(ME, hleDelayResult(0, "player create", delayUs));
+ INFO_LOG(ME, "psmfplayer create, psmfPlayerLibVersion 0x%0x, psmfPlayerLibcrc %x", psmfPlayerLibVersion, psmfPlayerLibcrc);
+ return hleDelayResult(0, "player create", delayUs);
}
static int scePsmfPlayerStop(u32 psmfPlayer) {
@@ -1427,7 +1435,7 @@ static int scePsmfPlayerStart(u32 psmfPlayer, u32 psmfPlayerData, int initPts)
psmfplayer->playMode = playerData->playMode;
psmfplayer->playSpeed = playerData->playSpeed;
- WARN_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d,(mode %d, speed %d)", psmfPlayer, psmfPlayerData, initPts, playerData->playMode, playerData->playSpeed);
+ WARN_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d (mode %d, speed %d)", psmfPlayer, psmfPlayerData, initPts, playerData->playMode, playerData->playSpeed);
// Does not alter current pts, it just catches up when Update()/etc. get there.
diff --git a/Core/HLE/scePsmf.h b/Core/HLE/scePsmf.h
index 3248962470..bb0e53c411 100644
--- a/Core/HLE/scePsmf.h
+++ b/Core/HLE/scePsmf.h
@@ -21,7 +21,7 @@ void Register_scePsmf();
void Register_scePsmfPlayer();
void __PsmfInit();
-void __PsmfPlayerLoadModule(int devkitVersion);
+void __PsmfPlayerLoadModule(int devkitVersion, u32 crc);
void __PsmfDoState(PointerWrap &p);
void __PsmfPlayerDoState(PointerWrap &p);
void __PsmfShutdown();
diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp
index e6a5124600..f1d7f73d0b 100644
--- a/Core/HLE/sceUtility.cpp
+++ b/Core/HLE/sceUtility.cpp
@@ -744,11 +744,14 @@ static int sceUtilityGamedataInstallInitStart(u32 paramsAddr) {
}
ActivateDialog(UtilityDialogType::GAMEDATAINSTALL);
- return hleLogSuccessInfoX(SCEUTILITY, gamedataInstallDialog->Init(paramsAddr));
+ int result = gamedataInstallDialog->Init(paramsAddr);
+ if (result < 0)
+ DeactivateDialog();
+ return hleLogSuccessInfoX(SCEUTILITY, result);
}
static int sceUtilityGamedataInstallShutdownStart() {
- if (currentDialogType != UtilityDialogType::GAMEDATAINSTALL) {
+ if (!currentDialogActive || currentDialogType != UtilityDialogType::GAMEDATAINSTALL) {
return hleLogWarning(SCEUTILITY, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type");
}
@@ -757,7 +760,7 @@ static int sceUtilityGamedataInstallShutdownStart() {
}
static int sceUtilityGamedataInstallUpdate(int animSpeed) {
- if (currentDialogType != UtilityDialogType::GAMEDATAINSTALL) {
+ if (!currentDialogActive || currentDialogType != UtilityDialogType::GAMEDATAINSTALL) {
return hleLogWarning(SCEUTILITY, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type");
}
@@ -765,8 +768,9 @@ static int sceUtilityGamedataInstallUpdate(int animSpeed) {
}
static int sceUtilityGamedataInstallGetStatus() {
- if (currentDialogType != UtilityDialogType::GAMEDATAINSTALL) {
+ if (!currentDialogActive || currentDialogType != UtilityDialogType::GAMEDATAINSTALL) {
// This is called incorrectly all the time by some games. So let's not bother warning.
+ hleEatCycles(200);
return hleLogDebug(SCEUTILITY, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type");
}
@@ -776,7 +780,7 @@ static int sceUtilityGamedataInstallGetStatus() {
}
static int sceUtilityGamedataInstallAbort() {
- if (currentDialogType != UtilityDialogType::GAMEDATAINSTALL) {
+ if (!currentDialogActive || currentDialogType != UtilityDialogType::GAMEDATAINSTALL) {
return hleLogWarning(SCEUTILITY, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type");
}
diff --git a/Core/HW/Display.cpp b/Core/HW/Display.cpp
index ebabb647d0..1dcb51e18c 100644
--- a/Core/HW/Display.cpp
+++ b/Core/HW/Display.cpp
@@ -265,7 +265,7 @@ void __DisplayListenVblank(VblankCallback callback) {
void __DisplayListenFlip(FlipCallback callback, void *userdata) {
std::lock_guard guard(listenersLock);
- flipListeners.push_back(std::make_pair(callback, userdata));
+ flipListeners.emplace_back(callback, userdata);
}
void __DisplayForgetFlip(FlipCallback callback, void *userdata) {
diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp
index 09ac48f47f..37722fe0cf 100644
--- a/Core/Loaders.cpp
+++ b/Core/Loaders.cpp
@@ -60,7 +60,7 @@ FileLoader *ConstructFileLoader(const Path &filename) {
// TODO : improve, look in the file more
IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorString) {
- *errorString = "";
+ errorString->clear();
if (fileLoader == nullptr) {
*errorString = "Invalid fileLoader";
return IdentifiedFileType::ERROR_IDENTIFYING;
diff --git a/Core/MIPS/JitCommon/JitBlockCache.cpp b/Core/MIPS/JitCommon/JitBlockCache.cpp
index b85b336fcd..f5b11ca4a5 100644
--- a/Core/MIPS/JitCommon/JitBlockCache.cpp
+++ b/Core/MIPS/JitCommon/JitBlockCache.cpp
@@ -198,7 +198,7 @@ void JitBlockCache::ProxyBlock(u32 rootAddress, u32 startAddress, u32 size, cons
// Make binary searches and stuff work ok
b.normalEntry = codePtr;
b.checkedEntry = codePtr;
- proxyBlockMap_.insert(std::make_pair(startAddress, num_blocks_));
+ proxyBlockMap_.emplace(startAddress, num_blocks_);
AddBlockMap(num_blocks_);
num_blocks_++; //commit the current block
@@ -253,7 +253,7 @@ void JitBlockCache::FinalizeBlock(int block_num, bool block_link) {
if (block_link) {
for (int i = 0; i < MAX_JIT_BLOCK_EXITS; i++) {
if (b.exitAddress[i] != INVALID_EXIT) {
- links_to_.insert(std::make_pair(b.exitAddress[i], block_num));
+ links_to_.emplace(b.exitAddress[i], block_num);
}
}
diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp
index 2c4e11bea4..81f87da663 100644
--- a/Core/MIPS/MIPSAnalyst.cpp
+++ b/Core/MIPS/MIPSAnalyst.cpp
@@ -771,7 +771,7 @@ namespace MIPSAnalyst {
for (auto iter = functions.begin(); iter != functions.end(); iter++) {
AnalyzedFunction &f = *iter;
if (f.hasHash && f.size > 16) {
- hashToFunction.insert(std::make_pair(f.hash, &f));
+ hashToFunction.emplace(f.hash, &f);
}
}
}
@@ -1420,6 +1420,7 @@ skip:
std::vector GetOutputRegs(MIPSOpcode op) {
std::vector vec;
+ vec.reserve(3);
MIPSInfo info = MIPSGetInfo(op);
if (info & OUT_RD) vec.push_back(MIPS_GET_RD(op));
if (info & OUT_RT) vec.push_back(MIPS_GET_RT(op));
diff --git a/Core/MIPS/MIPSAsm.cpp b/Core/MIPS/MIPSAsm.cpp
index 97d34a4f18..1c45f14ac5 100644
--- a/Core/MIPS/MIPSAsm.cpp
+++ b/Core/MIPS/MIPSAsm.cpp
@@ -1,10 +1,12 @@
#include
#include
#include
+#include
#include
#include "Common/CommonTypes.h"
#include "ext/armips/Core/Assembler.h"
+#include "ext/armips/Core/FileManager.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Core/Debugger/SymbolMap.h"
@@ -14,9 +16,9 @@
namespace MIPSAsm
{
- static std::wstring errorText;
+ static std::string errorText;
-std::wstring GetAssembleError()
+std::string GetAssembleError()
{
return errorText;
}
@@ -55,22 +57,21 @@ public:
return true;
}
bool seekPhysical(int64_t physicalAddress) override { return seekVirtual(physicalAddress); }
- const std::wstring &getFileName() override { return dummyWFilename_; }
+ const fs::path &getFileName() override { return dummyFilename_; }
private:
u64 address;
- std::wstring dummyWFilename_;
+ fs::path dummyFilename_;
};
-bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address)
-{
- StringList errors;
+bool MipsAssembleOpcode(const char *line, DebugInterface *cpu, u32 address) {
+ std::vector errors;
- wchar_t str[64];
- swprintf(str,64,L".psp\n.org 0x%08X\n",address);
+ char str[64];
+ snprintf(str, 64, ".psp\n.org 0x%08X\n", address);
ArmipsArguments args;
args.mode = ArmipsMode::MEMORY;
- args.content = str + ConvertUTF8ToWString(line);
+ args.content = str + std::string(line);
args.silent = true;
args.memoryFile.reset(new PspAssemblerFile());
args.errorsResult = &errors;
@@ -79,14 +80,14 @@ bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address)
g_symbolMap->GetLabels(args.labels);
}
- errorText = L"";
+ errorText.clear();
if (!runArmips(args))
{
for (size_t i = 0; i < errors.size(); i++)
{
errorText += errors[i];
- if (i != errors.size()-1)
- errorText += L"\n";
+ if (i != errors.size() - 1)
+ errorText += "\n";
}
return false;
diff --git a/Core/MIPS/MIPSAsm.h b/Core/MIPS/MIPSAsm.h
index 5693ff6c74..27bf3eef08 100644
--- a/Core/MIPS/MIPSAsm.h
+++ b/Core/MIPS/MIPSAsm.h
@@ -4,5 +4,5 @@
namespace MIPSAsm {
bool MipsAssembleOpcode(const char* line, DebugInterface* cpu, u32 address);
- std::wstring GetAssembleError();
+ std::string GetAssembleError();
}
diff --git a/Core/Reporting.cpp b/Core/Reporting.cpp
index f344e1bac9..4f15062e3c 100644
--- a/Core/Reporting.cpp
+++ b/Core/Reporting.cpp
@@ -434,7 +434,8 @@ namespace Reporting
void AddGameplayInfo(UrlEncoder &postdata)
{
// Just to get an idea of how long they played.
- postdata.Add("ticks", (const uint64_t)CoreTiming::GetTicks());
+ if (PSP_IsInited())
+ postdata.Add("ticks", (const uint64_t)CoreTiming::GetTicks());
float vps, fps;
__DisplayGetAveragedFPS(&vps, &fps);
diff --git a/Core/Screenshot.cpp b/Core/Screenshot.cpp
index c049756798..00f7d0a305 100644
--- a/Core/Screenshot.cpp
+++ b/Core/Screenshot.cpp
@@ -406,8 +406,7 @@ bool Save888RGBScreenshot(const Path &filename, ScreenshotFormat fmt, const u8 *
}
bool Save8888RGBAScreenshot(const Path &filename, const u8 *buffer, int w, int h) {
- png_image png;
- memset(&png, 0, sizeof(png));
+ png_image png{};
png.version = PNG_IMAGE_VERSION;
png.format = PNG_FORMAT_RGBA;
png.width = w;
@@ -421,3 +420,30 @@ bool Save8888RGBAScreenshot(const Path &filename, const u8 *buffer, int w, int h
}
return success;
}
+
+bool Save8888RGBAScreenshot(std::vector &bufferPNG, const u8 *bufferRGBA8888, int w, int h) {
+ png_image png{};
+ png.version = PNG_IMAGE_VERSION;
+ png.format = PNG_FORMAT_RGBA;
+ png.width = w;
+ png.height = h;
+
+ png_alloc_size_t allocSize = bufferPNG.size();
+ int result = png_image_write_to_memory(&png, allocSize == 0 ? nullptr : bufferPNG.data(), &allocSize, 0, bufferRGBA8888, w * 4, nullptr);
+ bool success = result != 0 && png.warning_or_error <= 1;
+ if (!success && allocSize != bufferPNG.size()) {
+ bufferPNG.resize(allocSize);
+ png.warning_or_error = 0;
+ result = png_image_write_to_memory(&png, bufferPNG.data(), &allocSize, 0, bufferRGBA8888, w * 4, nullptr);
+ success = result != 0 && png.warning_or_error <= 1;
+ }
+ if (success)
+ bufferPNG.resize(allocSize);
+ png_image_free(&png);
+
+ if (!success) {
+ ERROR_LOG(IO, "Buffering screenshot to PNG produced errors.");
+ bufferPNG.clear();
+ }
+ return success;
+}
diff --git a/Core/Screenshot.h b/Core/Screenshot.h
index 117cd5e625..ac124fef9a 100644
--- a/Core/Screenshot.h
+++ b/Core/Screenshot.h
@@ -39,5 +39,8 @@ const u8 *ConvertBufferToScreenshot(const GPUDebugBuffer &buf, bool alpha, u8 *&
// Can only be used while in game.
bool TakeGameScreenshot(const Path &filename, ScreenshotFormat fmt, ScreenshotType type, int *width = nullptr, int *height = nullptr, int maxRes = -1);
+
bool Save888RGBScreenshot(const Path &filename, ScreenshotFormat fmt, const u8 *bufferRGB888, int w, int h);
bool Save8888RGBAScreenshot(const Path &filename, const u8 *bufferRGBA8888, int w, int h);
+// Overallocate bufferPNG for better encoding speed.
+bool Save8888RGBAScreenshot(std::vector &bufferPNG, const u8 *bufferRGBA8888, int w, int h);
diff --git a/Core/System.cpp b/Core/System.cpp
index 29b1765fb0..cb6fc34fa8 100644
--- a/Core/System.cpp
+++ b/Core/System.cpp
@@ -114,6 +114,7 @@ static std::string gpuBackendDevice;
static volatile bool pspIsInited = false;
static volatile bool pspIsIniting = false;
static volatile bool pspIsQuitting = false;
+static volatile bool pspIsRebooting = false;
void ResetUIState() {
globalUIState = UISTATE_MENU;
@@ -413,11 +414,11 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) {
}
#if defined(_WIN32) && PPSSPP_ARCH(AMD64)
- INFO_LOG(BOOT, "PPSSPP %s Windows 64 bit", PPSSPP_GIT_VERSION);
+ NOTICE_LOG(BOOT, "PPSSPP %s Windows 64 bit", PPSSPP_GIT_VERSION);
#elif defined(_WIN32) && !PPSSPP_ARCH(AMD64)
- INFO_LOG(BOOT, "PPSSPP %s Windows 32 bit", PPSSPP_GIT_VERSION);
+ NOTICE_LOG(BOOT, "PPSSPP %s Windows 32 bit", PPSSPP_GIT_VERSION);
#else
- INFO_LOG(BOOT, "PPSSPP %s", PPSSPP_GIT_VERSION);
+ NOTICE_LOG(BOOT, "PPSSPP %s", PPSSPP_GIT_VERSION);
#endif
Core_NotifyLifecycle(CoreLifecycle::STARTING);
@@ -426,7 +427,7 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) {
if (g_CoreParameter.graphicsContext == nullptr) {
g_CoreParameter.graphicsContext = temp;
}
- g_CoreParameter.errorString = "";
+ g_CoreParameter.errorString.clear();
pspIsIniting = true;
PSP_SetLoading("Loading game...");
@@ -448,6 +449,7 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) {
bool success = !g_CoreParameter.fileToStart.empty();
if (!success) {
Core_NotifyLifecycle(CoreLifecycle::START_COMPLETE);
+ pspIsRebooting = false;
// In this case, we must call shutdown since the caller won't know to.
// It must've partially started since CPU_Init returned true.
PSP_Shutdown();
@@ -475,6 +477,7 @@ bool PSP_InitUpdate(std::string *error_string) {
}
}
if (!success) {
+ pspIsRebooting = false;
PSP_Shutdown();
return true;
}
@@ -483,6 +486,7 @@ bool PSP_InitUpdate(std::string *error_string) {
pspIsIniting = !pspIsInited;
if (pspIsInited) {
Core_NotifyLifecycle(CoreLifecycle::START_COMPLETE);
+ pspIsRebooting = false;
}
return pspIsInited;
}
@@ -501,7 +505,11 @@ bool PSP_IsIniting() {
}
bool PSP_IsInited() {
- return pspIsInited && !pspIsQuitting;
+ return pspIsInited && !pspIsQuitting && !pspIsRebooting;
+}
+
+bool PSP_IsRebooting() {
+ return pspIsRebooting;
}
bool PSP_IsQuitting() {
@@ -515,7 +523,7 @@ void PSP_Shutdown() {
}
// Make sure things know right away that PSP memory, etc. is going away.
- pspIsQuitting = true;
+ pspIsQuitting = !pspIsRebooting;
if (coreState == CORE_RUNNING)
Core_Stop();
@@ -540,6 +548,18 @@ void PSP_Shutdown() {
Core_NotifyLifecycle(CoreLifecycle::STOPPED);
}
+bool PSP_Reboot(std::string *error_string) {
+ if (!pspIsInited || pspIsQuitting)
+ return false;
+
+ pspIsRebooting = true;
+ Core_Stop();
+ Core_WaitInactive();
+ PSP_Shutdown();
+ std::string resetError;
+ return PSP_Init(PSP_CoreParameter(), error_string);
+}
+
void PSP_BeginHostFrame() {
// Reapply the graphics state of the PSP
if (gpu) {
diff --git a/Core/System.h b/Core/System.h
index 599836e259..9a801596ab 100644
--- a/Core/System.h
+++ b/Core/System.h
@@ -75,8 +75,10 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string);
bool PSP_InitUpdate(std::string *error_string);
bool PSP_IsIniting();
bool PSP_IsInited();
+bool PSP_IsRebooting();
bool PSP_IsQuitting();
void PSP_Shutdown();
+bool PSP_Reboot(std::string *error_string);
void PSP_BeginHostFrame();
void PSP_EndHostFrame();
diff --git a/Core/Util/AudioFormat.h b/Core/Util/AudioFormat.h
index 8774e83546..06d53676cd 100644
--- a/Core/Util/AudioFormat.h
+++ b/Core/Util/AudioFormat.h
@@ -49,7 +49,7 @@ static inline s16 clamp_s16(int i) {
static inline s16 ApplySampleVolume(s16 sample, int vol) {
#if PPSSPP_ARCH(ARM) && !defined(_MSC_VER)
- register int r;
+ int r;
asm volatile("smulwb %0, %1, %2\n\t" \
"ssat %0, #16, %0" \
: "=r"(r) : "r"(vol), "r"(sample));
diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp
index b6f4ccbd65..e808f07b6c 100644
--- a/Core/Util/GameManager.cpp
+++ b/Core/Util/GameManager.cpp
@@ -674,7 +674,7 @@ bool GameManager::InstallRawISO(const Path &file, const std::string &originalNam
void GameManager::ResetInstallError() {
if (!installInProgress_) {
- installError_ = "";
+ installError_.clear();
}
}
diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp
index 3d6d9b37cb..4aff212eba 100644
--- a/Core/WebServer.cpp
+++ b/Core/WebServer.cpp
@@ -297,7 +297,7 @@ static void ForwardDebuggerRequest(const http::Request &request) {
// Check if this is a websocket request...
std::string upgrade;
if (!request.GetHeader("upgrade", &upgrade)) {
- upgrade = "";
+ upgrade.clear();
}
// Yes - proceed with the socket.
diff --git a/GPU/Common/DepalettizeShaderCommon.cpp b/GPU/Common/DepalettizeShaderCommon.cpp
index ee04359135..a1e593e626 100644
--- a/GPU/Common/DepalettizeShaderCommon.cpp
+++ b/GPU/Common/DepalettizeShaderCommon.cpp
@@ -84,6 +84,9 @@ void GenerateDepalShader300(ShaderWriter &writer, const DepalConfig &config) {
int shiftedMask = mask << shift;
switch (config.bufferFormat) {
+ case GE_FORMAT_CLUT8:
+ writer.C(" int index = int(color.r * 255.99);\n");
+ break;
case GE_FORMAT_8888:
if (shiftedMask & 0xFF) writer.C(" int r = int(color.r * 255.99);\n"); else writer.C(" int r = 0;\n");
if (shiftedMask & 0xFF00) writer.C(" int g = int(color.g * 255.99);\n"); else writer.C(" int g = 0;\n");
@@ -168,6 +171,16 @@ void GenerateDepalShaderFloat(ShaderWriter &writer, const DepalConfig &config) {
// pixelformat is the format of the texture we are sampling.
bool formatOK = true;
switch (config.bufferFormat) {
+ case GE_FORMAT_CLUT8:
+ if (shift == 0 && mask == 0xFF) {
+ // Easy peasy.
+ sprintf(lookupMethod, "index.r");
+ formatOK = true;
+ } else {
+ // Deal with this if we find it.
+ formatOK = false;
+ }
+ break;
case GE_FORMAT_8888:
if ((mask & (mask + 1)) == 0) {
// If the value has all bits contiguous (bitmask check above), we can mod by it + 1.
diff --git a/GPU/Common/Draw2D.cpp b/GPU/Common/Draw2D.cpp
index ff7aab3169..05bead52e4 100644
--- a/GPU/Common/Draw2D.cpp
+++ b/GPU/Common/Draw2D.cpp
@@ -70,6 +70,23 @@ Draw2DPipelineInfo GenerateDraw2DCopyColorFs(ShaderWriter &writer) {
};
}
+Draw2DPipelineInfo GenerateDraw2DCopyColorRect2LinFs(ShaderWriter &writer) {
+ writer.DeclareSamplers(samplers);
+ writer.BeginFSMain(g_draw2Duniforms, varyings, FSFLAG_NONE);
+ writer.C(" vec2 tSize = texSize / scaleFactor;\n");
+ writer.C(" vec2 pixels = v_texcoord * tSize;\n");
+ writer.C(" float u = mod(pixels.x, tSize.x);\n");
+ writer.C(" float v = floor(pixels.x / tSize.x);\n");
+ writer.C(" vec4 outColor = ").SampleTexture2D("tex", "vec2(u, v) / tSize").C(";\n");
+ writer.EndFSMain("outColor", FSFLAG_NONE);
+
+ return Draw2DPipelineInfo{
+ "draw2d_copy_color_rect2lin",
+ RASTER_COLOR,
+ RASTER_COLOR,
+ };
+}
+
Draw2DPipelineInfo GenerateDraw2DCopyDepthFs(ShaderWriter &writer) {
writer.DeclareSamplers(samplers);
writer.BeginFSMain(Slice::empty(), varyings, FSFLAG_WRITEDEPTH);
@@ -161,9 +178,10 @@ void Draw2D::Ensure2DResources() {
const ShaderLanguageDesc &shaderLanguageDesc = draw_->GetShaderLanguageDesc();
if (!draw2DVs_) {
- char *vsCode = new char[4000];
+ char *vsCode = new char[8192];
ShaderWriter writer(vsCode, shaderLanguageDesc, ShaderStage::Vertex);
GenerateDraw2DVS(writer);
+ _assert_msg_(strlen(vsCode) < 8192, "Draw2D VS length error: %d", (int)strlen(vsCode));
draw2DVs_ = draw_->CreateShaderModule(ShaderStage::Vertex, shaderLanguageDesc.shaderLanguage, (const uint8_t *)vsCode, strlen(vsCode), "draw2d_vs");
_assert_(draw2DVs_);
delete[] vsCode;
@@ -198,9 +216,10 @@ Draw2DPipeline *Draw2D::Create2DPipeline(std::functionGetShaderLanguageDesc();
- char *fsCode = new char[4000];
+ char *fsCode = new char[8192];
ShaderWriter writer(fsCode, shaderLanguageDesc, ShaderStage::Fragment);
Draw2DPipelineInfo info = generate(writer);
+ _assert_msg_(strlen(fsCode) < 8192, "Draw2D FS length error: %d", (int)strlen(fsCode));
ShaderModule *fs = draw_->CreateShaderModule(ShaderStage::Fragment, shaderLanguageDesc.shaderLanguage, (const uint8_t *)fsCode, strlen(fsCode), info.tag);
@@ -318,6 +337,13 @@ Draw2DPipeline *FramebufferManagerCommon::Get2DPipeline(Draw2DShader shader) {
pipeline = draw2DPipelineColor_;
break;
+ case DRAW2D_COPY_COLOR_RECT2LIN:
+ if (!draw2DPipelineColorRect2Lin_) {
+ draw2DPipelineColorRect2Lin_ = draw2D_.Create2DPipeline(&GenerateDraw2DCopyColorRect2LinFs);
+ }
+ pipeline = draw2DPipelineColorRect2Lin_;
+ break;
+
case DRAW2D_COPY_DEPTH:
if (!draw_->GetDeviceCaps().fragmentShaderDepthWriteSupported) {
// Can't do it
diff --git a/GPU/Common/Draw2D.h b/GPU/Common/Draw2D.h
index d44f7ff613..bf156e6d39 100644
--- a/GPU/Common/Draw2D.h
+++ b/GPU/Common/Draw2D.h
@@ -16,6 +16,7 @@ enum Draw2DShader {
DRAW2D_COPY_DEPTH,
DRAW2D_565_TO_DEPTH,
DRAW2D_565_TO_DEPTH_DESWIZZLE,
+ DRAW2D_COPY_COLOR_RECT2LIN,
};
inline RasterChannel Draw2DSourceChannel(Draw2DShader shader) {
diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp
index d361458111..f884486c65 100644
--- a/GPU/Common/DrawEngineCommon.cpp
+++ b/GPU/Common/DrawEngineCommon.cpp
@@ -19,6 +19,7 @@
#include "Common/Data/Convert/ColorConv.h"
#include "Common/Profiler/Profiler.h"
+#include "Common/LogReporting.h"
#include "Core/Config.h"
#include "GPU/Common/DrawEngineCommon.h"
#include "GPU/Common/SplineCommon.h"
@@ -188,6 +189,57 @@ u32 DrawEngineCommon::NormalizeVertices(u8 *outPtr, u8 *bufPtr, const u8 *inPtr,
return DrawEngineCommon::NormalizeVertices(outPtr, bufPtr, inPtr, dec, lowerBound, upperBound, vertType);
}
+void DrawEngineCommon::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation) {
+ // Instead of plumbing through properly (we'd need to inject these pretransformed vertices in the middle
+ // of SoftwareTransform(), which would take a lot of refactoring), we'll cheat and just turn these into
+ // through vertices.
+ // Since the only known use is Thrillville and it only uses it to clear, we just use color and pos.
+ struct ImmVertex {
+ float uv[2];
+ uint32_t color;
+ float xyz[3];
+ };
+ std::vector temp;
+ temp.resize(vertexCount);
+ uint32_t color1Used = 0;
+ for (int i = 0; i < vertexCount; i++) {
+ // Since we're sending through, scale back up to w/h.
+ temp[i].uv[0] = buffer[i].u * gstate.getTextureWidth(0);
+ temp[i].uv[1] = buffer[i].v * gstate.getTextureHeight(0);
+ temp[i].color = buffer[i].color0_32;
+ temp[i].xyz[0] = buffer[i].pos[0];
+ temp[i].xyz[1] = buffer[i].pos[1];
+ temp[i].xyz[2] = buffer[i].pos[2];
+ color1Used |= buffer[i].color1_32;
+ }
+ int vtype = GE_VTYPE_TC_FLOAT | GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_THROUGH;
+ // TODO: Handle fog and secondary color somehow?
+
+ if (gstate.isFogEnabled() && !gstate.isModeThrough()) {
+ WARN_LOG_REPORT_ONCE(geimmfog, G3D, "Imm vertex used fog");
+ }
+ if (color1Used != 0 && gstate.isUsingSecondaryColor() && !gstate.isModeThrough()) {
+ WARN_LOG_REPORT_ONCE(geimmcolor1, G3D, "Imm vertex used secondary color");
+ }
+
+ bool prevThrough = gstate.isModeThrough();
+ // Code checks this reg directly, not just the vtype ID.
+ if (!prevThrough) {
+ gstate.vertType |= GE_VTYPE_THROUGH;
+ gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET | DIRTY_CULLRANGE);
+ }
+
+ int bytesRead;
+ uint32_t vertTypeID = GetVertTypeID(vtype, 0);
+ SubmitPrim(&temp[0], nullptr, prim, vertexCount, vertTypeID, cullMode, &bytesRead);
+ DispatchFlush();
+
+ if (!prevThrough) {
+ gstate.vertType &= ~GE_VTYPE_THROUGH;
+ gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET | DIRTY_CULLRANGE);
+ }
+}
+
// This code has plenty of potential for optimization.
//
// It does the simplest and safest test possible: If all points of a bbox is outside a single of
@@ -484,12 +536,12 @@ u32 DrawEngineCommon::NormalizeVertices(u8 *outPtr, u8 *bufPtr, const u8 *inPtr,
return GE_VTYPE_TC_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_NRM_FLOAT | GE_VTYPE_POS_FLOAT | (vertType & (GE_VTYPE_IDX_MASK | GE_VTYPE_THROUGH));
}
-void DrawEngineCommon::ApplyFramebufferRead(bool *fboTexNeedsBind) {
+void DrawEngineCommon::ApplyFramebufferRead(FBOTexState *fboTexState) {
if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) {
- *fboTexNeedsBind = false;
+ *fboTexState = FBO_TEX_READ_FRAMEBUFFER;
} else {
gpuStats.numCopiesForShaderBlend++;
- *fboTexNeedsBind = true;
+ *fboTexState = FBO_TEX_COPY_BIND_TEX;
}
gstate_c.Dirty(DIRTY_SHADERBLEND);
@@ -665,6 +717,16 @@ uint64_t DrawEngineCommon::ComputeHash() {
return fullhash;
}
+// Cheap bit scrambler from https://nullprogram.com/blog/2018/07/31/
+inline uint32_t lowbias32_r(uint32_t x) {
+ x ^= x >> 16;
+ x *= 0x43021123U;
+ x ^= x >> 15 ^ x >> 30;
+ x *= 0x1d69e2a5U;
+ x ^= x >> 16;
+ return x;
+}
+
// vertTypeID is the vertex type but with the UVGen mode smashed into the top bits.
void DrawEngineCommon::SubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, int cullMode, int *bytesRead) {
if (!indexGen.PrimCompatible(prevPrim_, prim) || numDrawCalls >= MAX_DEFERRED_DRAW_CALLS || vertexCountInDrawCalls_ + vertexCount > VERTEX_BUFFER_MAX) {
@@ -693,10 +755,10 @@ void DrawEngineCommon::SubmitPrim(const void *verts, const void *inds, GEPrimiti
if (g_Config.bVertexCache) {
u32 dhash = dcid_;
dhash = __rotl(dhash ^ (u32)(uintptr_t)verts, 13);
- dhash = __rotl(dhash ^ (u32)(uintptr_t)inds, 13);
- dhash = __rotl(dhash ^ (u32)vertTypeID, 13);
- dhash = __rotl(dhash ^ (u32)vertexCount, 13);
- dcid_ = dhash ^ (u32)prim;
+ dhash = __rotl(dhash ^ (u32)(uintptr_t)inds, 19);
+ dhash = __rotl(dhash ^ (u32)vertTypeID, 7);
+ dhash = __rotl(dhash ^ (u32)vertexCount, 11);
+ dcid_ = lowbias32_r(dhash ^ (u32)prim);
}
DeferredDrawCall &dc = drawCalls[numDrawCalls];
diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h
index a8997454d9..8fc0382a29 100644
--- a/GPU/Common/DrawEngineCommon.h
+++ b/GPU/Common/DrawEngineCommon.h
@@ -46,6 +46,12 @@ enum {
TEX_SLOT_SPLINE_WEIGHTS_V = 6,
};
+enum FBOTexState {
+ FBO_TEX_NONE,
+ FBO_TEX_COPY_BIND_TEX,
+ FBO_TEX_READ_FRAMEBUFFER,
+};
+
inline uint32_t GetVertTypeID(uint32_t vertType, int uvGenMode) {
// As the decoder depends on the UVGenMode when we use UV prescale, we simply mash it
// into the top of the verttype where there are unused bits.
@@ -84,10 +90,7 @@ public:
SubmitPrim(verts, inds, prim, vertexCount, vertTypeID, cullMode, bytesRead);
}
- virtual void DispatchSubmitImm(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, int cullMode, int *bytesRead) {
- SubmitPrim(verts, inds, prim, vertexCount, vertTypeID, cullMode, bytesRead);
- DispatchFlush();
- }
+ virtual void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation);
bool TestBoundingBox(const void* control_points, int vertexCount, u32 vertType, int *bytesRead);
@@ -130,7 +133,7 @@ protected:
// Vertex decoding
void DecodeVertsStep(u8 *dest, int &i, int &decodedVerts);
- void ApplyFramebufferRead(bool *fboTexNeedsBind);
+ void ApplyFramebufferRead(FBOTexState *fboTexState);
inline int IndexSize(u32 vtype) const {
const u32 indexType = (vtype & GE_VTYPE_IDX_MASK);
@@ -144,6 +147,8 @@ protected:
bool useHWTransform_ = false;
bool useHWTessellation_ = false;
+ // Used to prevent unnecessary flushing in softgpu.
+ bool flushOnParams_ = true;
// Vertex collector buffers
u8 *decoded = nullptr;
diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp
index 9586d01b48..2f8f248dbd 100644
--- a/GPU/Common/FragmentShaderGenerator.cpp
+++ b/GPU/Common/FragmentShaderGenerator.cpp
@@ -134,10 +134,12 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
GELogicOp replaceLogicOpType = isModeClear ? GE_LOGIC_COPY : (GELogicOp)id.Bits(FS_BIT_REPLACE_LOGIC_OP, 4);
bool replaceLogicOp = replaceLogicOpType != GE_LOGIC_COPY && compat.bitwiseOps;
- bool readFramebuffer = replaceBlend == REPLACE_BLEND_READ_FRAMEBUFFER || colorWriteMask || replaceLogicOp;
- bool readFramebufferTex = readFramebuffer && !gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH);
+ bool needFramebufferRead = replaceBlend == REPLACE_BLEND_READ_FRAMEBUFFER || colorWriteMask || replaceLogicOp;
- bool needFragCoord = readFramebuffer || gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT);
+ bool fetchFramebuffer = needFramebufferRead && gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH);
+ bool readFramebufferTex = needFramebufferRead && !gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH);
+
+ bool needFragCoord = readFramebufferTex || gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT);
bool writeDepth = gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT);
if (shaderDepalMode != ShaderDepalMode::OFF && !doTexture) {
@@ -157,6 +159,11 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
if (readFramebufferTex) {
WRITE(p, "layout (binding = 1) uniform sampler2D fbotex;\n");
+ } else if (fetchFramebuffer) {
+ WRITE(p, "layout (input_attachment_index = 0, binding = 9) uniform subpassInput inputColor;\n");
+ if (fragmentShaderFlags) {
+ *fragmentShaderFlags |= FragmentShaderFlags::INPUT_ATTACHMENT;
+ }
}
if (shaderDepalMode != ShaderDepalMode::OFF) {
@@ -167,9 +174,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, "layout (location = 1) %s in lowp vec4 v_color0;\n", shading);
if (lmode)
WRITE(p, "layout (location = 2) %s in lowp vec3 v_color1;\n", shading);
- if (enableFog) {
- WRITE(p, "layout (location = 3) in highp float v_fogdepth;\n");
- }
+ WRITE(p, "layout (location = 3) in highp float v_fogdepth;\n");
if (doTexture) {
WRITE(p, "layout (location = 0) in highp vec3 v_texcoord;\n");
}
@@ -178,7 +183,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, "int roundAndScaleTo255i(in highp float x) { return int(floor(x * 255.0 + 0.5)); }\n");
}
if (enableColorTest && !colorTestAgainstZero) {
- WRITE(p, "ivec3 roundAndScaleTo255iv(in highp vec3 x) { return ivec3(floor(x * 255.0 + 0.5)); }\n");
+ WRITE(p, "uint roundAndScaleTo8x4(in highp vec3 x) { uvec3 u = uvec3(floor(x * 255.0 + 0.5)); return u.r | (u.g << 8) | (u.b << 16); }\n");
+ WRITE(p, "uint packFloatsTo8x4(in vec3 x) { uvec3 u = uvec3(x); return u.r | (u.g << 8) | (u.b << 16); }\n");
}
WRITE(p, "layout (location = 0, index = 0) out vec4 fragColor0;\n");
@@ -257,7 +263,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
}
if (enableColorTest) {
if (compat.shaderLanguage == HLSL_D3D11) {
- WRITE(p, "uvec3 roundAndScaleTo255iv(float3 x) { return (floor(x * 255.0f + 0.5f)); }\n");
+ WRITE(p, "uint roundAndScaleTo8x4(float3 x) { uvec3 u = (floor(x * 255.0f + 0.5f)); return u.r | (u.g << 8) | (u.b << 16); }\n");
+ WRITE(p, "uint packFloatsTo8x4(in vec3 x) { uvec3 u = uvec3(x); return u.r | (u.g << 8) | (u.b << 16); }\n");
} else {
WRITE(p, "vec3 roundAndScaleTo255v(float3 x) { return floor(x * 255.0f + 0.5f); }\n");
}
@@ -272,9 +279,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
if (lmode) {
WRITE(p, " vec3 v_color1: COLOR1;\n");
}
- if (enableFog) {
- WRITE(p, " float v_fogdepth: TEXCOORD1;\n");
- }
+ WRITE(p, " float v_fogdepth: TEXCOORD1;\n");
if (needFragCoord) {
if (compat.shaderLanguage == HLSL_D3D11) {
WRITE(p, " vec4 pixelPos : SV_POSITION;\n");
@@ -351,7 +356,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, "uniform vec4 u_alphacolorref;\n");
if (compat.bitwiseOps && ((enableColorTest && !colorTestAgainstZero) || (enableAlphaTest && !alphaTestAgainstZero))) {
*uniformMask |= DIRTY_ALPHACOLORMASK;
- WRITE(p, "uniform ivec4 u_alphacolormask;\n");
+ WRITE(p, "uniform uint u_alphacolormask;\n");
}
}
}
@@ -387,8 +392,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
if (enableFog) {
*uniformMask |= DIRTY_FOGCOLOR;
WRITE(p, "uniform vec3 u_fogcolor;\n");
- WRITE(p, "%s %s float v_fogdepth;\n", compat.varying_fs, highpFog ? "highp" : "mediump");
}
+ WRITE(p, "%s %s float v_fogdepth;\n", compat.varying_fs, highpFog ? "highp" : "mediump");
if (doTexture) {
WRITE(p, "%s %s vec3 v_texcoord;\n", compat.varying_fs, highpTexcoord ? "highp" : "mediump");
}
@@ -405,7 +410,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
}
if (enableColorTest && !colorTestAgainstZero) {
if (compat.bitwiseOps) {
- WRITE(p, "ivec3 roundAndScaleTo255iv(in vec3 x) { return ivec3(floor(x * 255.0 + 0.5)); }\n");
+ WRITE(p, "uint roundAndScaleTo8x4(in vec3 x) { uvec3 u = uvec3(floor(x * 255.99)); return u.r | (u.g << 8) | (u.b << 16); }\n");
+ WRITE(p, "uint packFloatsTo8x4(in vec3 x) { uvec3 u = uvec3(x); return u.r | (u.g << 8) | (u.b << 16); }\n");
} else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) {
WRITE(p, "vec3 roundTo255thv(in vec3 x) { vec3 y = x + (0.5/255.0); return y - fract(y * 255.0) * (1.0 / 255.0); }\n");
} else {
@@ -416,7 +422,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
if (!strcmp(compat.fragColor0, "fragColor0")) {
const char *qualifierColor0 = "out";
- if (readFramebuffer && compat.lastFragData && !strcmp(compat.lastFragData, compat.fragColor0)) {
+ if (fetchFramebuffer && compat.lastFragData && !strcmp(compat.lastFragData, compat.fragColor0)) {
qualifierColor0 = "inout";
}
// Output the output color definitions.
@@ -455,6 +461,12 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, "}\n");
}
+ if (compat.bitwiseOps && enableColorTest) {
+ p.C("uvec3 unpackUVec3(highp uint x) {\n");
+ p.C(" return uvec3(x & 0xFFU, (x >> 8) & 0xFFU, (x >> 16) & 0xFFU);\n");
+ p.C("}\n");
+ }
+
// PowerVR needs a custom modulo function. For some reason, this has far higher precision than the builtin one.
if ((gl_extensions.bugs & BUG_PVR_SHADER_PRECISION_BAD) && needShaderTexClamp) {
WRITE(p, "float mymod(float a, float b) { return a - b * floor(a / b); }\n");
@@ -492,20 +504,26 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
}
// Two things read from the old framebuffer - shader replacement blending and bit-level masking.
- if (readFramebuffer) {
+ if (readFramebufferTex) {
if (compat.shaderLanguage == HLSL_D3D11) {
WRITE(p, " vec4 destColor = fbotex.Load(int3((int)gl_FragCoord.x, (int)gl_FragCoord.y, 0));\n");
} else if (compat.shaderLanguage == HLSL_D3D9) {
WRITE(p, " vec4 destColor = tex2D(fbotex, gl_FragCoord.xy * u_fbotexSize.xy);\n", compat.texture);
- } else if (gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH)) {
- // If we have EXT_shader_framebuffer_fetch / ARM_shader_framebuffer_fetch, we skip the blit.
- // We can just read the prev value more directly.
- WRITE(p, " lowp vec4 destColor = %s;\n", compat.lastFragData);
} else if (!compat.texelFetch) {
WRITE(p, " lowp vec4 destColor = %s(fbotex, gl_FragCoord.xy * u_fbotexSize.xy);\n", compat.texture);
} else {
WRITE(p, " lowp vec4 destColor = %s(fbotex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0);\n", compat.texelFetch);
}
+ } else if (fetchFramebuffer) {
+ // If we have EXT_shader_framebuffer_fetch / ARM_shader_framebuffer_fetch, we skip the blit.
+ // We can just read the prev value more directly.
+ if (compat.shaderLanguage == GLSL_3xx) {
+ WRITE(p, " lowp vec4 destColor = %s;\n", compat.lastFragData);
+ } else if (compat.shaderLanguage == GLSL_VULKAN) {
+ WRITE(p, " lowp vec4 destColor = subpassLoad(inputColor);\n", compat.lastFragData);
+ } else {
+ _assert_msg_(false, "Need fetch destColor, but not a compatible language");
+ }
}
if (isModeClear) {
@@ -734,8 +752,27 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, " }\n");
break;
case ShaderDepalMode::CLUT8_8888:
- // Not yet implemented.
- WRITE(p, " vec4 t = vec4(0.0, 0.0, 0.0, 0.0);\n");
+ if (doTextureProjection) {
+ // We don't use textureProj because we need better control and it's probably not much of a savings anyway.
+ // However it is good for precision on older hardware like PowerVR.
+ p.F(" vec2 uv = %s.xy/%s.z;\n vec2 uv_round;\n", texcoord, texcoord);
+ } else {
+ p.F(" vec2 uv = %s.xy;\n vec2 uv_round;\n", texcoord);
+ }
+ p.C(" vec2 tsize = vec2(textureSize(tex, 0).xy);\n");
+ p.C(" uv_round = floor(uv * tsize);\n");
+ p.C(" int component = int(uv_round.x) & 3;\n");
+ p.C(" uv_round.x *= 0.25;\n");
+ p.C(" uv_round /= tsize;\n");
+ p.C(" vec4 t = ").SampleTexture2D("tex", "uv_round").C(";\n");
+ p.C(" int index;\n");
+ p.C(" switch (component) {\n");
+ p.C(" case 0: index = int(t.x * 254.99); break;\n"); // TODO: Not sure why 254.99 instead of 255.99, but it's currently needed.
+ p.C(" case 1: index = int(t.y * 254.99); break;\n");
+ p.C(" case 2: index = int(t.z * 254.99); break;\n");
+ p.C(" case 3: index = int(t.w * 254.99); break;\n");
+ p.C(" }\n");
+ p.C(" t = ").LoadTexture2D("pal", "ivec2(index, 0)", 0).C(";\n");
break;
}
@@ -845,7 +882,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
const char *alphaTestFuncs[] = { "#", "#", " != ", " == ", " >= ", " > ", " <= ", " < " };
if (alphaTestFuncs[alphaTestFunc][0] != '#') {
if (compat.bitwiseOps) {
- WRITE(p, " if ((roundAndScaleTo255i(v.a) & u_alphacolormask.a) %s int(u_alphacolorref.a)) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement);
+ WRITE(p, " if ((roundAndScaleTo255i(v.a) & int(u_alphacolormask >> 24)) %s int(u_alphacolorref.a)) %s\n", alphaTestFuncs[alphaTestFunc], discardStatement);
} else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) {
// Work around bad PVR driver problem where equality check + discard just doesn't work.
if (alphaTestFunc != GE_COMP_NOTEQUAL) {
@@ -899,34 +936,22 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
}
} else {
const char *colorTestFuncs[] = { "#", "#", " != ", " == " };
- if (colorTestFuncs[colorTestFunc][0] != '#') {
+ const char *test = colorTestFuncs[colorTestFunc];
+ if (test[0] != '#') {
// TODO: Unify these paths better.
- if (compat.shaderLanguage == HLSL_D3D11) {
- const char *test = colorTestFuncs[colorTestFunc];
- WRITE(p, " uvec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n");
- WRITE(p, " uvec3 v_masked = v_scaled & u_alphacolormask.rgb;\n");
- WRITE(p, " uvec3 colorTestRef = u_alphacolorref.rgb & u_alphacolormask.rgb;\n");
- // We have to test the components separately, or we get incorrect results. See #10629.
- WRITE(p, " if (v_masked.r %s colorTestRef.r && v_masked.g %s colorTestRef.g && v_masked.b %s colorTestRef.b) %s\n", test, test, test, discardStatement);
- } else if (compat.shaderLanguage == HLSL_D3D9) {
- const char *test = colorTestFuncs[colorTestFunc];
+ if (compat.shaderLanguage == HLSL_D3D9) {
// TODO: Use a texture to lookup bitwise ops instead?
WRITE(p, " vec3 colortest = roundAndScaleTo255v(v.rgb);\n");
WRITE(p, " if ((colortest.r %s u_alphacolorref.r) && (colortest.g %s u_alphacolorref.g) && (colortest.b %s u_alphacolorref.b)) %s\n", test, test, test, discardStatement);
} else if (compat.bitwiseOps) {
- WRITE(p, " ivec3 v_scaled = roundAndScaleTo255iv(v.rgb);\n");
- if (compat.shaderLanguage == GLSL_VULKAN) {
- // Apparently GLES3 does not support vector bitwise ops, but Vulkan does?
- WRITE(p, " if ((v_scaled & u_alphacolormask.rgb) %s (u_alphacolorref.rgb & u_alphacolormask.rgb)) %s\n", colorTestFuncs[colorTestFunc], discardStatement);
- } else {
- const char *maskedFragColor = "ivec3(v_scaled.r & u_alphacolormask.r, v_scaled.g & u_alphacolormask.g, v_scaled.b & u_alphacolormask.b)";
- const char *maskedColorRef = "ivec3(int(u_alphacolorref.r) & u_alphacolormask.r, int(u_alphacolorref.g) & u_alphacolormask.g, int(u_alphacolorref.b) & u_alphacolormask.b)";
- WRITE(p, " if (%s %s %s) %s\n", maskedFragColor, colorTestFuncs[colorTestFunc], maskedColorRef, discardStatement);
- }
+ WRITE(p, " uint v_uint = roundAndScaleTo8x4(v.rgb);\n");
+ WRITE(p, " uint v_masked = v_uint & u_alphacolormask;\n");
+ WRITE(p, " uint colorTestRef = packFloatsTo8x4(u_alphacolorref.rgb) & u_alphacolormask;\n");
+ WRITE(p, " if (v_masked %s colorTestRef) %s\n", test, discardStatement);
} else if (gl_extensions.gpuVendor == GPU_VENDOR_IMGTEC) {
- WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) %s\n", colorTestFuncs[colorTestFunc], discardStatement);
+ WRITE(p, " if (roundTo255thv(v.rgb) %s u_alphacolorref.rgb) %s\n", test, discardStatement);
} else {
- WRITE(p, " if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) %s\n", colorTestFuncs[colorTestFunc], discardStatement);
+ WRITE(p, " if (roundAndScaleTo255v(v.rgb) %s u_alphacolorref.rgb) %s\n", test, discardStatement);
}
} else {
WRITE(p, " %s\n", discardStatement);
@@ -1126,7 +1151,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
case GE_LOGIC_COPY_INVERTED: p.C(" v32 = (~v32 & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break;
case GE_LOGIC_OR_INVERTED: p.C(" v32 = ((~v32 | d32) & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break;
case GE_LOGIC_NAND: p.C(" v32 = (~(v32 & d32) & 0x00FFFFFFu) | (v32 & 0xFF000000u);\n"); break;
- case GE_LOGIC_SET: p.C(" v32 |= 0x00FFFFFF;\n"); break;
+ case GE_LOGIC_SET: p.C(" v32 |= 0x00FFFFFFu;\n"); break;
}
// Note that the mask has already been flipped to the PC way - 1 means write.
diff --git a/GPU/Common/FragmentShaderGenerator.h b/GPU/Common/FragmentShaderGenerator.h
index 85c651cf8b..8f358fa7ff 100644
--- a/GPU/Common/FragmentShaderGenerator.h
+++ b/GPU/Common/FragmentShaderGenerator.h
@@ -42,7 +42,7 @@ struct FShaderID;
// Can technically be deduced from the fragment shader ID, but this is safer.
enum class FragmentShaderFlags : u32 {
- FS_FLAG_INPUT_ATTACHMENT = 1,
+ INPUT_ATTACHMENT = 1,
};
ENUM_CLASS_BITOPS(FragmentShaderFlags);
diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp
index bac8514562..df0342724a 100644
--- a/GPU/Common/FramebufferManagerCommon.cpp
+++ b/GPU/Common/FramebufferManagerCommon.cpp
@@ -49,6 +49,10 @@
#include "GPU/GPUInterface.h"
#include "GPU/GPUState.h"
+static size_t FormatFramebufferName(VirtualFramebuffer *vfb, char *tag, size_t len) {
+ return snprintf(tag, len, "FB_%08x_%08x_%dx%d_%s", vfb->fb_address, vfb->z_address, vfb->bufferWidth, vfb->bufferHeight, GeBufferFormatToString(vfb->fb_format));
+}
+
FramebufferManagerCommon::FramebufferManagerCommon(Draw::DrawContext *draw)
: draw_(draw), draw2D_(draw_) {
presentation_ = new PresentationCommon(draw);
@@ -108,7 +112,9 @@ void FramebufferManagerCommon::BeginFrame() {
}
void FramebufferManagerCommon::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) {
- displayFramebufPtr_ = framebuf;
+ displayFramebufPtr_ = framebuf & 0x3FFFFFFF;
+ if (Memory::IsVRAMAddress(displayFramebufPtr_))
+ displayFramebufPtr_ = framebuf & 0x041FFFFF;
displayStride_ = stride;
displayFormat_ = format;
GPUDebug::NotifyDisplay(framebuf, stride, format);
@@ -117,6 +123,8 @@ void FramebufferManagerCommon::SetDisplayFramebuffer(u32 framebuf, u32 stride, G
VirtualFramebuffer *FramebufferManagerCommon::GetVFBAt(u32 addr) const {
addr &= 0x3FFFFFFF;
+ if (Memory::IsVRAMAddress(addr))
+ addr &= 0x041FFFFF;
VirtualFramebuffer *match = nullptr;
for (auto vfb : vfbs_) {
if (vfb->fb_address == addr) {
@@ -130,6 +138,9 @@ VirtualFramebuffer *FramebufferManagerCommon::GetVFBAt(u32 addr) const {
}
VirtualFramebuffer *FramebufferManagerCommon::GetExactVFB(u32 addr, int stride, GEBufferFormat format) const {
+ addr &= 0x3FFFFFFF;
+ if (Memory::IsVRAMAddress(addr))
+ addr &= 0x041FFFFF;
VirtualFramebuffer *newest = nullptr;
for (auto vfb : vfbs_) {
if (vfb->fb_address == addr && vfb->fb_stride == stride && vfb->fb_format == format) {
@@ -146,6 +157,9 @@ VirtualFramebuffer *FramebufferManagerCommon::GetExactVFB(u32 addr, int stride,
}
VirtualFramebuffer *FramebufferManagerCommon::ResolveVFB(u32 addr, int stride, GEBufferFormat format) {
+ addr &= 0x3FFFFFFF;
+ if (Memory::IsVRAMAddress(addr))
+ addr &= 0x041FFFFF;
// Find the newest one matching addr and stride.
VirtualFramebuffer *newest = nullptr;
for (auto vfb : vfbs_) {
@@ -231,7 +245,7 @@ void FramebufferManagerCommon::EstimateDrawingSize(u32 fb_address, int fb_stride
// The majority of the time, these are equal. If not, let's check what we know.
u32 nearest_address = 0xFFFFFFFF;
for (auto vfb : vfbs_) {
- const u32 other_address = vfb->fb_address & 0x3FFFFFFF;
+ const u32 other_address = vfb->fb_address;
if (other_address > fb_address && other_address < nearest_address) {
nearest_address = other_address;
}
@@ -292,10 +306,11 @@ void FramebufferManagerCommon::EstimateDrawingSize(u32 fb_address, int fb_stride
}
void GetFramebufferHeuristicInputs(FramebufferHeuristicParams *params, const GPUgstate &gstate) {
- params->fb_address = (gstate.getFrameBufRawAddress() & 0x3FFFFFFF) | 0x04000000; // GetFramebufferHeuristicInputs is only called from rendering, and thus, it's VRAM.
+ // GetFramebufferHeuristicInputs is only called from rendering, and thus, it's VRAM.
+ params->fb_address = gstate.getFrameBufRawAddress() | 0x04000000;
params->fb_stride = gstate.FrameBufStride();
- params->z_address = (gstate.getDepthBufRawAddress() & 0x3FFFFFFF) | 0x04000000;
+ params->z_address = gstate.getDepthBufRawAddress() | 0x04000000;
params->z_stride = gstate.DepthBufStride();
if (params->z_address == params->fb_address) {
@@ -475,9 +490,9 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer
vfb->fb_format = params.fb_format;
vfb->usageFlags = FB_USAGE_RENDER_COLOR;
- u32 byteSize = ColorBufferByteSize(vfb);
- if (Memory::IsVRAMAddress(params.fb_address) && params.fb_address + byteSize > framebufRangeEnd_) {
- framebufRangeEnd_ = params.fb_address + byteSize;
+ u32 colorByteSize = ColorBufferByteSize(vfb);
+ if (Memory::IsVRAMAddress(params.fb_address) && params.fb_address + colorByteSize > framebufRangeEnd_) {
+ framebufRangeEnd_ = params.fb_address + colorByteSize;
}
// This is where we actually create the framebuffer. The true is "force".
@@ -499,9 +514,9 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer
// Assume that if we're clearing right when switching to a new framebuffer, we don't need to upload.
if (useBufferedRendering_ && params.isDrawing) {
- gpu->PerformMemoryUpload(params.fb_address, byteSize);
+ gpu->PerformMemoryUpload(params.fb_address, colorByteSize);
// Alpha was already done by PerformMemoryUpload.
- PerformStencilUpload(params.fb_address, byteSize, StencilUpload::STENCIL_IS_ZERO | StencilUpload::IGNORE_ALPHA);
+ PerformStencilUpload(params.fb_address, colorByteSize, StencilUpload::STENCIL_IS_ZERO | StencilUpload::IGNORE_ALPHA);
// TODO: Is it worth trying to upload the depth buffer (only if it wasn't copied above..?)
}
@@ -547,13 +562,42 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) {
return;
}
+ // First time use of this framebuffer's depth buffer.
+ bool newlyUsingDepth = (currentRenderVfb_->usageFlags & FB_USAGE_RENDER_DEPTH) == 0;
+ currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH;
+
+ uint32_t boundDepthBuffer = gstate.getDepthBufRawAddress() | 0x04000000;
+ if (currentRenderVfb_->z_address != boundDepthBuffer) {
+ WARN_LOG_N_TIMES(z_reassign, 5, G3D, "Framebuffer at %08x/%d has switched associated depth buffer from %08x to %08x, updating.",
+ currentRenderVfb_->fb_address, currentRenderVfb_->fb_stride, currentRenderVfb_->z_address, boundDepthBuffer);
+
+ // Technically, here we should copy away the depth buffer to another framebuffer that uses that z_address, or maybe
+ // even write it back to RAM. However, this is rare. Silent Hill is one example, see #16126.
+ currentRenderVfb_->z_address = boundDepthBuffer;
+
+ if (currentRenderVfb_->fbo) {
+ char tag[128];
+ FormatFramebufferName(currentRenderVfb_, tag, sizeof(tag));
+ currentRenderVfb_->fbo->UpdateTag(tag);
+ }
+ }
+
// If this first draw call is anything other than a clear, "resolve" the depth buffer,
// by copying from any overlapping buffers with fresher content.
- if (!isClearingDepth) {
+ if (!isClearingDepth && useBufferedRendering_) {
CopyToDepthFromOverlappingFramebuffers(currentRenderVfb_);
+
+ // Need to upload the first line of depth buffers, for Burnout Dominator lens flares. See issue #11100 and comments to #16081.
+ // Might make this more generic and upload the whole depth buffer if we find it's needed for something.
+ if (newlyUsingDepth && draw_->GetDeviceCaps().fragmentShaderDepthWriteSupported) {
+ // Sanity check the depth buffer pointer.
+ if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) {
+ const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address);
+ DrawPixels(currentRenderVfb_, 0, 0, (const u8 *)src, GE_FORMAT_DEPTH16, currentRenderVfb_->z_stride, currentRenderVfb_->width, currentRenderVfb_->height, RASTER_DEPTH, "Depth Upload");
+ }
+ }
}
- currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH;
currentRenderVfb_->depthBindSeq = GetBindSeqCount();
}
@@ -633,7 +677,7 @@ void FramebufferManagerCommon::CopyToDepthFromOverlappingFramebuffers(VirtualFra
}
}
- gstate_c.Dirty(DIRTY_TEXTURE_IMAGE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_BLEND_STATE);
+ gstate_c.Dirty(DIRTY_ALL_RENDER_STATE);
}
// Can't easily dynamically create these strings, we just pass along the pointer.
@@ -811,6 +855,11 @@ void FramebufferManagerCommon::CopyToColorFromOverlappingFramebuffers(VirtualFra
}
Draw2DPipeline *FramebufferManagerCommon::GetReinterpretPipeline(GEBufferFormat from, GEBufferFormat to, float *scaleFactorX) {
+ if (from == to) {
+ *scaleFactorX = 1.0f;
+ return Get2DPipeline(DRAW2D_COPY_COLOR);
+ }
+
if (IsBufferFormat16Bit(from) && !IsBufferFormat16Bit(to)) {
// We halve the X coordinates in the destination framebuffer.
// The shader will collect two pixels worth of input data and merge into one.
@@ -819,6 +868,8 @@ Draw2DPipeline *FramebufferManagerCommon::GetReinterpretPipeline(GEBufferFormat
// We double the X coordinates in the destination framebuffer.
// The shader will sample and depending on the X coordinate & 1, use the upper or lower bits.
*scaleFactorX = 2.0f;
+ } else {
+ *scaleFactorX = 1.0f;
}
Draw2DPipeline *pipeline = reinterpretFromTo_[(int)from][(int)to];
@@ -894,7 +945,7 @@ void FramebufferManagerCommon::BlitFramebufferDepth(VirtualFramebuffer *src, Vir
// Some GPUs can copy depth but only if stencil gets to come along for the ride. We only want to use this if there is no blit functionality.
if (useCopy) {
- draw_->CopyFramebufferImage(src->fbo, 0, 0, 0, 0, dst->fbo, 0, 0, 0, 0, w, h, 1, Draw::FB_DEPTH_BIT, "BlitFramebufferDepth");
+ draw_->CopyFramebufferImage(src->fbo, 0, 0, 0, 0, dst->fbo, 0, 0, 0, 0, w, h, 1, Draw::FB_DEPTH_BIT, "CopyFramebufferDepth");
RebindFramebuffer("After BlitFramebufferDepth");
} else if (useBlit) {
// We'll accept whether we get a separate depth blit or not...
@@ -944,7 +995,7 @@ void FramebufferManagerCommon::NotifyRenderFramebufferSwitched(VirtualFramebuffe
if (useBufferedRendering_) {
if (vfb->fbo) {
shaderManager_->DirtyLastShader();
- draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, "FramebufferSwitch");
+ draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, "FBSwitch");
} else {
// This should only happen very briefly when toggling useBufferedRendering_.
ResizeFramebufFBO(vfb, vfb->width, vfb->height, true);
@@ -997,10 +1048,12 @@ void FramebufferManagerCommon::NotifyVideoUpload(u32 addr, int size, int stride,
void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) {
// Take off the uncached flag from the address. Not to be confused with the start of VRAM.
addr &= 0x3FFFFFFF;
+ if (Memory::IsVRAMAddress(addr))
+ addr &= 0x041FFFFF;
// TODO: Could go through all FBOs, but probably not important?
// TODO: Could also check for inner changes, but video is most important.
// TODO: This shouldn't care if it's a display framebuf or not, should work exactly the same.
- bool isDisplayBuf = addr == DisplayFramebufAddr() || addr == PrevDisplayFramebufAddr();
+ bool isDisplayBuf = addr == CurrentDisplayFramebufAddr() || addr == PrevDisplayFramebufAddr();
// TODO: Deleting the FBO is a heavy hammer solution, so let's only do it if it'd help.
if (!Memory::IsValidAddress(displayFramebufPtr_))
return;
@@ -1017,7 +1070,7 @@ void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) {
// TODO: This doesn't seem quite right anymore.
fmt = displayFormat_;
}
- DrawPixels(vfb, 0, 0, Memory::GetPointer(addr), fmt, vfb->fb_stride, vfb->width, vfb->height);
+ DrawPixels(vfb, 0, 0, Memory::GetPointer(addr), fmt, vfb->fb_stride, vfb->width, vfb->height, RASTER_COLOR, "UpdateFromMemory_DrawPixels");
SetColorUpdated(vfb, gstate_c.skipDrawReason);
} else {
INFO_LOG(FRAMEBUF, "Invalidating FBO for %08x (%dx%d %s)", vfb->fb_address, vfb->width, vfb->height, GeBufferFormatToString(vfb->fb_format));
@@ -1033,20 +1086,24 @@ void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) {
gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE);
}
-void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height) {
+void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, RasterChannel channel, const char *tag) {
textureCache_->ForgetLastTexture();
- shaderManager_->DirtyLastShader(); // On GL, important that this is BEFORE drawing
+ shaderManager_->DirtyLastShader();
float u0 = 0.0f, u1 = 1.0f;
float v0 = 0.0f, v1 = 1.0f;
DrawTextureFlags flags;
if (useBufferedRendering_ && vfb && vfb->fbo) {
- flags = DRAWTEX_LINEAR;
- draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, "DrawPixels");
- gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE);
+ if (channel == RASTER_DEPTH || PSP_CoreParameter().compat.flags().NearestFilteringOnFramebufferCreate) {
+ flags = DRAWTEX_NEAREST;
+ } else {
+ flags = DRAWTEX_LINEAR;
+ }
+ draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, tag);
SetViewport2D(0, 0, vfb->renderWidth, vfb->renderHeight);
draw_->SetScissorRect(0, 0, vfb->renderWidth, vfb->renderHeight);
} else {
+ _dbg_assert_(channel == RASTER_COLOR);
// We are drawing directly to the back buffer so need to flip.
// Should more of this be handled by the presentation engine?
if (needBackBufferYSwap_)
@@ -1060,15 +1117,23 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int
draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_);
}
+ if (channel == RASTER_DEPTH) {
+ _dbg_assert_(srcPixelFormat == GE_FORMAT_DEPTH16);
+ flags = flags | DRAWTEX_DEPTH;
+ }
+
Draw::Texture *pixelsTex = MakePixelTexture(srcPixels, srcPixelFormat, srcStride, width, height);
if (pixelsTex) {
draw_->BindTextures(0, 1, &pixelsTex);
+
+ // TODO: Replace with draw2D_.Blit() directly.
DrawActiveTexture(dstX, dstY, width, height, vfb->bufferWidth, vfb->bufferHeight, u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags);
+
gpuStats.numUploads++;
pixelsTex->Release();
draw_->InvalidateCachedState();
- gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
+ gstate_c.Dirty(DIRTY_ALL_RENDER_STATE);
}
}
@@ -1153,6 +1218,7 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
const u16_le *src16 = (const u16_le *)srcPixels + srcStride * y;
const u32_le *src32 = (const u32_le *)srcPixels + srcStride * y;
u32 *dst = (u32 *)(data + byteStride * y);
+ u16 *dst16 = (u16 *)(data + byteStride * y);
switch (srcPixelFormat) {
case GE_FORMAT_565:
if (preferredPixelsFormat_ == Draw::DataFormat::B8G8R8A8_UNORM)
@@ -1185,18 +1251,28 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
memcpy(dst, src32, width * 4);
break;
- case GE_FORMAT_INVALID:
case GE_FORMAT_DEPTH16:
- _dbg_assert_msg_(false, "Invalid pixelFormat passed to DrawPixels().");
+ // TODO: Must take the depth range into account, unless it's already 0-1.
+ // TODO: Depending on the color buffer format used with this depth buffer, we need
+ // to do one of two different swizzle operations. However, for the only use of this so far,
+ // the Burnout lens flare trickery, swizzle doesn't matter since it's just a 0, 7fff, 0, 7fff pattern
+ // which comes out the same.
+ memcpy(dst16, src16, w * 2);
+ break;
+
+ case GE_FORMAT_INVALID:
+ // Bad
break;
}
}
return true;
};
+ // Note: For depth, we create an R16_UNORM texture, that'll be just fine for uploading depth through a shader,
+ // and likely more efficient.
Draw::TextureDesc desc{
Draw::TextureType::LINEAR2D,
- preferredPixelsFormat_,
+ srcPixelFormat == GE_FORMAT_DEPTH16 ? Draw::DataFormat::R16_UNORM : preferredPixelsFormat_,
width,
height,
1,
@@ -1206,11 +1282,12 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
{ (uint8_t *)srcPixels },
generateTexture,
};
+
// Hot Shots Golf (#12355) does tons of these in a frame in some situations! So creating textures
// better be fast.
Draw::Texture *tex = draw_->CreateTexture(desc);
if (!tex)
- ERROR_LOG(G3D, "Failed to create drawpixels texture");
+ ERROR_LOG(G3D, "Failed to create DrawPixels texture");
return tex;
}
@@ -1229,7 +1306,7 @@ void FramebufferManagerCommon::DrawFramebufferToOutput(const u8 *srcPixels, int
if (needBackBufferYSwap_) {
flags |= OutputFlags::BACKBUFFER_FLIPPED;
}
- // DrawActiveTexture reverses these, probably to match "up".
+ // CopyToOutput reverses these, probably to match "up".
if (GetGPUBackend() == GPUBackend::DIRECT3D9 || GetGPUBackend() == GPUBackend::DIRECT3D11) {
flags |= OutputFlags::POSITION_FLIPPED;
}
@@ -1293,10 +1370,16 @@ void FramebufferManagerCommon::CopyDisplayToOutput(bool reallyDirty) {
// Let's search for a framebuf within this range. Note that we also look for
// "framebuffers" sitting in RAM (created from block transfer or similar) so we only take off the kernel
// and uncached bits of the address when comparing.
- const u32 addr = fbaddr & 0x3FFFFFFF;
+ const u32 addr = fbaddr;
for (auto v : vfbs_) {
- const u32 v_addr = v->fb_address & 0x3FFFFFFF;
+ const u32 v_addr = v->fb_address;
const u32 v_size = ColorBufferByteSize(v);
+
+ if (v->fb_format != displayFormat_ || v->fb_stride != displayStride_) {
+ // Displaying a buffer of the wrong format or stride is nonsense, ignore it.
+ continue;
+ }
+
if (addr >= v_addr && addr < v_addr + v_size) {
const u32 dstBpp = BufferFormatBytesPerPixel(v->fb_format);
const u32 v_offsetX = ((addr - v_addr) / dstBpp) % v->fb_stride;
@@ -1442,10 +1525,6 @@ void FramebufferManagerCommon::DecimateFBOs() {
}
}
-static size_t FormatFramebufferName(VirtualFramebuffer *vfb, char *tag, size_t len) {
- return snprintf(tag, len, "FB_%08x_%08x_%dx%d_%s", vfb->fb_address, vfb->z_address, vfb->bufferWidth, vfb->bufferHeight, GeBufferFormatToString(vfb->fb_format));
-}
-
// Requires width/height to be set already.
void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, int h, bool force, bool skipCopy) {
_dbg_assert_(w > 0);
@@ -1497,9 +1576,9 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w,
bool creating = old.bufferWidth == 0;
if (creating) {
- WARN_LOG(FRAMEBUF, "Creating %s FBO at %08x/%d %dx%d (force=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->fb_stride, vfb->bufferWidth, vfb->bufferHeight, (int)force);
+ WARN_LOG(FRAMEBUF, "Creating %s FBO at %08x/%08x stride=%d %dx%d (force=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->z_address, vfb->fb_stride, vfb->bufferWidth, vfb->bufferHeight, (int)force);
} else {
- WARN_LOG(FRAMEBUF, "Resizing %s FBO at %08x/%d from %dx%d to %dx%d (force=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->fb_stride, old.bufferWidth, old.bufferHeight, vfb->bufferWidth, vfb->bufferHeight, (int)force);
+ WARN_LOG(FRAMEBUF, "Resizing %s FBO at %08x/%08x stride=%d from %dx%d to %dx%d (force=%d, skipCopy=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->z_address, vfb->fb_stride, old.bufferWidth, old.bufferHeight, vfb->bufferWidth, vfb->bufferHeight, (int)force, (int)skipCopy);
}
// During hardware rendering, we always render at full color depth even if the game wouldn't on real hardware.
@@ -1537,8 +1616,10 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w,
if (vfb->fbo) {
draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }, "ResizeFramebufFBO");
if (!skipCopy) {
- BlitFramebuffer(vfb, 0, 0, &old, 0, 0, std::min((u16)oldWidth, std::min(vfb->bufferWidth, vfb->width)), std::min((u16)oldHeight, std::min(vfb->height, vfb->bufferHeight)), 0, RASTER_COLOR, "Blit_ResizeFramebufFBO");
- // Depth copying is handled by deferred copies later.
+ BlitFramebuffer(vfb, 0, 0, &old, 0, 0, std::min((u16)oldWidth, std::min(vfb->bufferWidth, vfb->width)), std::min((u16)oldHeight, std::min(vfb->height, vfb->bufferHeight)), 0, RASTER_COLOR, "BlitColor_ResizeFramebufFBO");
+ }
+ if (vfb->usageFlags & FB_USAGE_RENDER_DEPTH) {
+ BlitFramebuffer(vfb, 0, 0, &old, 0, 0, std::min((u16)oldWidth, std::min(vfb->bufferWidth, vfb->width)), std::min((u16)oldHeight, std::min(vfb->height, vfb->bufferHeight)), 0, RASTER_DEPTH, "BlitDepth_ResizeFramebufFBO");
}
}
fbosToDelete_.push_back(old.fbo);
@@ -1556,18 +1637,27 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w,
// This is called from detected memcopies and framebuffer initialization from VRAM. Not block transfers.
// MotoGP goes this path so we need to catch those copies here.
-bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, bool isMemset, u32 skipDrawReason) {
+bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, GPUCopyFlag flags, u32 skipDrawReason) {
if (size == 0) {
return false;
}
dst &= 0x3FFFFFFF;
src &= 0x3FFFFFFF;
+ if (Memory::IsVRAMAddress(dst))
+ dst &= 0x041FFFFF;
+ if (Memory::IsVRAMAddress(src))
+ src &= 0x041FFFFF;
- // TODO: Merge the below into FindTransferFramebuffer
+ // TODO: Merge the below into FindTransferFramebuffer.
+ // Or at least this should be like the other ones, gathering possible candidates
+ // with the ability to list them out for debugging.
+
+ VirtualFramebuffer *dstBuffer = nullptr;
+ VirtualFramebuffer *srcBuffer = nullptr;
+ bool ignoreDstBuffer = flags & GPUCopyFlag::FORCE_DST_MEM;
+ bool ignoreSrcBuffer = flags & (GPUCopyFlag::FORCE_SRC_MEM | GPUCopyFlag::MEMSET);
- VirtualFramebuffer *dstBuffer = 0;
- VirtualFramebuffer *srcBuffer = 0;
u32 dstY = (u32)-1;
u32 dstH = 0;
u32 srcY = (u32)-1;
@@ -1578,13 +1668,21 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size,
}
// We only remove the kernel and uncached bits when comparing.
- const u32 vfb_address = vfb->fb_address & 0x3FFFFFFF;
+ const u32 vfb_address = vfb->fb_address;
const u32 vfb_size = ColorBufferByteSize(vfb);
const u32 vfb_bpp = BufferFormatBytesPerPixel(vfb->fb_format);
const u32 vfb_byteStride = vfb->fb_stride * vfb_bpp;
const int vfb_byteWidth = vfb->width * vfb_bpp;
- if (dst >= vfb_address && (dst + size <= vfb_address + vfb_size || dst == vfb_address)) {
+ // Heuristic to try to prevent potential glitches with video playback.
+ if (!ignoreDstBuffer && vfb_address == dst && (size == 0x44000 && vfb_size == 0x88000 || size == 0x88000 && vfb_size == 0x44000)) {
+ // Not likely to be a correct color format copy for this buffer. Ignore it, there will either be RAM
+ // that can be displayed from, or another matching buffer with the right format if rendering is going on.
+ WARN_LOG_N_TIMES(notify_copy_2x, 5, G3D, "Framebuffer size %08x conspicuously not matching copy size %08x in NotifyFramebufferCopy. Ignoring.", size, vfb_size);
+ continue;
+ }
+
+ if (!ignoreDstBuffer && dst >= vfb_address && (dst + size <= vfb_address + vfb_size || dst == vfb_address)) {
const u32 offset = dst - vfb_address;
const u32 yOffset = offset / vfb_byteStride;
if ((offset % vfb_byteStride) == 0 && (size == vfb_byteWidth || (size % vfb_byteStride) == 0) && yOffset < dstY) {
@@ -1594,7 +1692,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size,
}
}
- if (src >= vfb_address && (src + size <= vfb_address + vfb_size || src == vfb_address)) {
+ if (!ignoreSrcBuffer && src >= vfb_address && (src + size <= vfb_address + vfb_size || src == vfb_address)) {
const u32 offset = src - vfb_address;
const u32 yOffset = offset / vfb_byteStride;
if ((offset % vfb_byteStride) == 0 && (size == vfb_byteWidth || (size % vfb_byteStride) == 0) && yOffset < srcY) {
@@ -1636,7 +1734,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size,
dstBuffer->last_frame_used = gpuStats.numFlips;
}
- if (dstBuffer && srcBuffer && !isMemset) {
+ if (dstBuffer && srcBuffer) {
if (srcBuffer == dstBuffer) {
WARN_LOG_ONCE(dstsrccpy, G3D, "Intra-buffer memcpy (not supported) %08x -> %08x (size: %x)", src, dst, size);
} else {
@@ -1648,13 +1746,13 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size,
}
return false;
} else if (dstBuffer) {
- if (isMemset) {
+ if (flags & GPUCopyFlag::MEMSET) {
gpuStats.numClears++;
}
WARN_LOG_ONCE(btucpy, G3D, "Memcpy fbo upload %08x -> %08x (size: %x)", src, dst, size);
FlushBeforeCopy();
const u8 *srcBase = Memory::GetPointerUnchecked(src);
- DrawPixels(dstBuffer, 0, dstY, srcBase, dstBuffer->fb_format, dstBuffer->fb_stride, dstBuffer->width, dstH);
+ DrawPixels(dstBuffer, 0, dstY, srcBase, dstBuffer->fb_format, dstBuffer->fb_stride, dstBuffer->width, dstH, RASTER_COLOR, "MemcpyFboUpload_DrawPixels");
SetColorUpdated(dstBuffer, skipDrawReason);
RebindFramebuffer("RebindFramebuffer - Memcpy fbo upload");
// This is a memcpy, let's still copy just in case.
@@ -1683,6 +1781,8 @@ std::string BlockTransferRect::ToString() const {
// for depth data yet.
bool FramebufferManagerCommon::FindTransferFramebuffer(u32 basePtr, int stride_pixels, int x_pixels, int y, int w_pixels, int h, int bpp, bool destination, BlockTransferRect *rect) {
basePtr &= 0x3FFFFFFF;
+ if (Memory::IsVRAMAddress(basePtr))
+ basePtr &= 0x041FFFFF;
rect->vfb = nullptr;
if (!stride_pixels) {
@@ -1700,8 +1800,17 @@ bool FramebufferManagerCommon::FindTransferFramebuffer(u32 basePtr, int stride_p
// that of their buffers. Then after matching we try to map the copy to the simplest operation that does
// what we need.
+ // We are only looking at color for now, have not found any block transfers of depth data (although it's plausible).
+
for (auto vfb : vfbs_) {
- const u32 vfb_address = vfb->fb_address & 0x3FFFFFFF;
+ // Check for easily detected depth copies for logging purposes.
+ // Depth copies are not that useful though because you manually need to account for swizzle, so
+ // not sure if games will use them.
+ if (vfb->z_address == basePtr) {
+ WARN_LOG_N_TIMES(z_xfer, 5, G3D, "FindTransferFramebuffer: found matching depth buffer, %08x (dest=%d, bpp=%d)", basePtr, (int)destination, bpp);
+ }
+
+ const u32 vfb_address = vfb->fb_address;
const u32 vfb_size = ColorBufferByteSize(vfb);
if (basePtr < vfb_address || basePtr >= vfb_address + vfb_size) {
@@ -1773,7 +1882,14 @@ bool FramebufferManagerCommon::FindTransferFramebuffer(u32 basePtr, int stride_p
// Sort candidates by just recency for now, we might add other.
for (size_t i = 0; i < candidates.size(); i++) {
const BlockTransferRect *candidate = &candidates[i];
- if (!best || candidate->vfb->colorBindSeq > best->vfb->colorBindSeq) {
+
+ bool better = !best || candidate->vfb->colorBindSeq > best->vfb->colorBindSeq;
+ if ((candidate->vfb->usageFlags & FB_USAGE_CLUT) && candidate->x_bytes == 0 && candidate->y == 0 && destination) {
+ // Hack to prioritize copies to clut buffers.
+ best = candidate;
+ break;
+ }
+ if (better) {
best = candidate;
}
}
@@ -1807,7 +1923,8 @@ VirtualFramebuffer *FramebufferManagerCommon::CreateRAMFramebuffer(uint32_t fbAd
// create a new one each frame.
VirtualFramebuffer *vfb = new VirtualFramebuffer{};
vfb->fbo = nullptr;
- vfb->fb_address = fbAddress; // NOTE - not necessarily in VRAM!
+ uint32_t mask = Memory::IsVRAMAddress(fbAddress) ? 0x041FFFFF : 0x3FFFFFFF;
+ vfb->fb_address = fbAddress & mask; // NOTE - not necessarily in VRAM!
vfb->fb_stride = stride;
vfb->z_address = 0; // marks that if anyone tries to render to this framebuffer, it should be dropped and recreated.
vfb->z_stride = 0;
@@ -1883,6 +2000,7 @@ VirtualFramebuffer *FramebufferManagerCommon::FindDownloadTempBuffer(VirtualFram
nvfb->fbo = draw_->CreateFramebuffer({ nvfb->bufferWidth, nvfb->bufferHeight, 1, 1, channel == RASTER_DEPTH ? true : false, name });
if (!nvfb->fbo) {
ERROR_LOG(FRAMEBUF, "Error creating FBO! %d x %d", nvfb->renderWidth, nvfb->renderHeight);
+ delete nvfb;
return nullptr;
}
bvfbs_.push_back(nvfb);
@@ -2125,7 +2243,7 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS
// We may still do a partial block draw below if this doesn't pass.
if (!useBufferedRendering_ && dstStride >= 480 && width >= 480 && height == 272) {
bool isPrevDisplayBuffer = PrevDisplayFramebufAddr() == dstBasePtr;
- bool isDisplayBuffer = DisplayFramebufAddr() == dstBasePtr;
+ bool isDisplayBuffer = CurrentDisplayFramebufAddr() == dstBasePtr;
if (isPrevDisplayBuffer || isDisplayBuffer) {
FlushBeforeCopy();
DrawFramebufferToOutput(Memory::GetPointerUnchecked(dstBasePtr), dstStride, displayFormat_);
@@ -2150,14 +2268,16 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS
}
if (dstBuffer && !srcBuffer) {
- WARN_LOG_ONCE(btu, G3D, "Block transfer upload %08x -> %08x", srcBasePtr, dstBasePtr);
+ WARN_LOG_ONCE(btu, G3D, "Block transfer upload %08x -> %08x (%dx%d %d,%d bpp=%d)", srcBasePtr, dstBasePtr, width, height, dstX, dstY, bpp);
FlushBeforeCopy();
const u8 *srcBase = Memory::GetPointerUnchecked(srcBasePtr) + (srcX + srcY * srcStride) * bpp;
+
int dstBpp = BufferFormatBytesPerPixel(dstRect.vfb->fb_format);
float dstXFactor = (float)bpp / dstBpp;
if (dstRect.w_bytes / bpp > dstRect.vfb->width || dstRect.h > dstRect.vfb->height) {
- // The buffer isn't big enough, and we have a clear hint of size. Resize.
+ // The buffer isn't big enough, and we have a clear hint of size. Resize.
// This happens in Valkyrie Profile when uploading video at the ending.
+ // Also happens to the CLUT framebuffer in the Burnout Dominator lens flare effect. See #16075
ResizeFramebufFBO(dstRect.vfb, dstRect.w_bytes / bpp, dstRect.h, false, true);
// Make sure we don't flop back and forth.
dstRect.vfb->newWidth = std::max(dstRect.w_bytes / bpp, (int)dstRect.vfb->width);
@@ -2166,7 +2286,7 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS
// Resizing may change the viewport/etc.
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE);
}
- DrawPixels(dstRect.vfb, static_cast(dstX * dstXFactor), dstY, srcBase, dstRect.vfb->fb_format, static_cast(srcStride * dstXFactor), static_cast(dstRect.w_bytes / bpp * dstXFactor), dstRect.h);
+ DrawPixels(dstRect.vfb, static_cast(dstX * dstXFactor), dstY, srcBase, dstRect.vfb->fb_format, static_cast(srcStride * dstXFactor), static_cast(dstRect.w_bytes / bpp * dstXFactor), dstRect.h, RASTER_COLOR, "BlockTransferCopy_DrawPixels");
SetColorUpdated(dstRect.vfb, skipDrawReason);
RebindFramebuffer("RebindFramebuffer - NotifyBlockTransferAfter");
}
@@ -2299,8 +2419,8 @@ void FramebufferManagerCommon::ShowScreenResolution() {
// * Save state screenshots(could probably be async but need to manage the stall.)
bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEBufferFormat format, GPUDebugBuffer &buffer, int maxScaleFactor) {
VirtualFramebuffer *vfb = currentRenderVfb_;
- if (!vfb) {
- vfb = GetVFBAt(fb_address);
+ if (!vfb || vfb->fb_address != fb_address) {
+ vfb = ResolveVFB(fb_address, fb_stride, format);
}
if (!vfb) {
@@ -2450,7 +2570,7 @@ void FramebufferManagerCommon::PackFramebufferSync(VirtualFramebuffer *vfb, int
return;
}
- const u32 fb_address = vfb->fb_address & 0x3FFFFFFF;
+ const u32 fb_address = vfb->fb_address;
Draw::DataFormat destFormat = channel == RASTER_COLOR ? GEFormatToThin3D(vfb->fb_format) : GEFormatToThin3D(GE_FORMAT_DEPTH16);
const int dstBpp = (int)DataFormatSizeInBytes(destFormat);
@@ -2643,6 +2763,7 @@ void FramebufferManagerCommon::DeviceLost() {
DoRelease(stencilUploadSampler_);
DoRelease(stencilUploadPipeline_);
DoRelease(draw2DPipelineColor_);
+ DoRelease(draw2DPipelineColorRect2Lin_);
DoRelease(draw2DPipelineDepth_);
DoRelease(draw2DPipeline565ToDepth_);
DoRelease(draw2DPipeline565ToDepthDeswizzle_);
@@ -2706,9 +2827,9 @@ void FramebufferManagerCommon::DrawActiveTexture(float x, float y, float w, floa
// Rearrange to strip form.
std::swap(coord[2], coord[3]);
- draw2D_.DrawStrip2D(nullptr, coord, 4, (flags & DRAWTEX_LINEAR) != 0, Get2DPipeline(DRAW2D_COPY_COLOR));
+ draw2D_.DrawStrip2D(nullptr, coord, 4, (flags & DRAWTEX_LINEAR) != 0, Get2DPipeline((flags & DRAWTEX_DEPTH) ? DRAW2D_COPY_DEPTH : DRAW2D_COPY_COLOR));
- gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE);
+ gstate_c.Dirty(DIRTY_ALL_RENDER_STATE);
}
void FramebufferManagerCommon::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp, RasterChannel channel, const char *tag) {
@@ -2721,6 +2842,11 @@ void FramebufferManagerCommon::BlitFramebuffer(VirtualFramebuffer *dst, int dstX
return;
}
+ if (channel == RASTER_DEPTH && !draw_->GetDeviceCaps().fragmentShaderDepthWriteSupported) {
+ // Can't do anything :(
+ return;
+ }
+
// Perform a little bit of clipping first.
// Block transfer coords are unsigned so I don't think we need to clip on the left side.. Although there are
// other uses for BlitFramebuffer.
@@ -2812,7 +2938,7 @@ void FramebufferManagerCommon::BlitFramebuffer(VirtualFramebuffer *dst, int dstX
draw_->InvalidateCachedState();
- gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE);
+ gstate_c.Dirty(DIRTY_ALL_RENDER_STATE);
}
// The input is raw pixel coordinates, scale not taken into account.
@@ -2848,7 +2974,7 @@ void FramebufferManagerCommon::BlitUsingRaster(
draw2D_.Blit(pipeline, srcX1, srcY1, srcX2, srcY2, destX1, destY1, destX2, destY2, (float)srcW, (float)srcH, (float)destW, (float)destH, linearFilter, scaleFactor);
- gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE);
+ gstate_c.Dirty(DIRTY_ALL_RENDER_STATE);
}
VirtualFramebuffer *FramebufferManagerCommon::ResolveFramebufferColorToFormat(VirtualFramebuffer *src, GEBufferFormat newFormat) {
@@ -2926,6 +3052,10 @@ static void ApplyKillzoneFramebufferSplit(FramebufferHeuristicParams *params, in
margin = true;
}
}
+
+ // TODO: Implement this for Burnout Dominator. It has to handle self-reads inside
+ // the margin framebuffer though, so framebuffer copies are still needed, just smaller.
+ // It uses 0x0080019f (through, float texcoords, ABGR 8888 colors, float positions).
}
if (margin) {
diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h
index 905996979b..b3bd21d7c9 100644
--- a/GPU/Common/FramebufferManagerCommon.h
+++ b/GPU/Common/FramebufferManagerCommon.h
@@ -192,6 +192,7 @@ enum DrawTextureFlags {
DRAWTEX_NEAREST = 0,
DRAWTEX_LINEAR = 1,
DRAWTEX_TO_BACKBUFFER = 8,
+ DRAWTEX_DEPTH = 16,
};
inline DrawTextureFlags operator | (const DrawTextureFlags &lhs, const DrawTextureFlags &rhs) {
@@ -305,7 +306,7 @@ public:
void CopyDisplayToOutput(bool reallyDirty);
- bool NotifyFramebufferCopy(u32 src, u32 dest, int size, bool isMemset, u32 skipDrawReason);
+ bool NotifyFramebufferCopy(u32 src, u32 dest, int size, GPUCopyFlag flags, u32 skipDrawReason);
void NotifyVideoUpload(u32 addr, int size, int width, GEBufferFormat fmt);
void UpdateFromMemory(u32 addr, int size);
void ApplyClearToMemory(int x1, int y1, int x2, int y2, u32 clearColor);
@@ -327,22 +328,25 @@ public:
void DownloadFramebufferForClut(u32 fb_address, u32 loadBytes);
void DrawFramebufferToOutput(const u8 *srcPixels, int srcStride, GEBufferFormat srcPixelFormat);
- void DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height);
+ void DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, RasterChannel channel, const char *tag);
size_t NumVFBs() const { return vfbs_.size(); }
u32 PrevDisplayFramebufAddr() const {
return prevDisplayFramebuf_ ? prevDisplayFramebuf_->fb_address : 0;
}
- u32 DisplayFramebufAddr() const {
+ u32 CurrentDisplayFramebufAddr() const {
return displayFramebuf_ ? displayFramebuf_->fb_address : 0;
}
+ u32 DisplayFramebufAddr() const {
+ return displayFramebufPtr_;
+ }
u32 DisplayFramebufStride() const {
- return displayFramebuf_ ? displayStride_ : 0;
+ return displayStride_;
}
GEBufferFormat DisplayFramebufFormat() const {
- return displayFramebuf_ ? displayFormat_ : GE_FORMAT_INVALID;
+ return displayFormat_;
}
bool UseBufferedRendering() const {
@@ -351,7 +355,9 @@ public:
bool MayIntersectFramebuffer(u32 start) const {
// Clear the cache/kernel bits.
- start = start & 0x3FFFFFFF;
+ start &= 0x3FFFFFFF;
+ if (Memory::IsVRAMAddress(start))
+ start &= 0x041FFFFF;
// Most games only have two framebuffers at the start.
if (start >= framebufRangeEnd_ || start < PSP_GetVidMemBase()) {
return false;
@@ -422,8 +428,18 @@ public:
VirtualFramebuffer *ResolveFramebufferColorToFormat(VirtualFramebuffer *vfb, GEBufferFormat newFormat);
Draw2DPipeline *Get2DPipeline(Draw2DShader shader);
+
+ // If from==to, returns a copy pipeline.
Draw2DPipeline *GetReinterpretPipeline(GEBufferFormat from, GEBufferFormat to, float *scaleFactorX);
+ // Public to be used from the texture cache's depal shenanigans.
+ void BlitUsingRaster(
+ Draw::Framebuffer *src, float srcX1, float srcY1, float srcX2, float srcY2,
+ Draw::Framebuffer *dest, float destX1, float destY1, float destX2, float destY2,
+ bool linearFilter,
+ int scaleFactor, // usually unused, except for swizzle...
+ Draw2DPipeline *pipeline, const char *tag);
+
protected:
virtual void PackFramebufferSync(VirtualFramebuffer *vfb, int x, int y, int w, int h, RasterChannel channel);
void SetViewport2D(int x, int y, int w, int h);
@@ -441,13 +457,6 @@ protected:
// Used by ReadFramebufferToMemory and later framebuffer block copies
void BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp, RasterChannel channel, const char *tag);
- void BlitUsingRaster(
- Draw::Framebuffer *src, float srcX1, float srcY1, float srcX2, float srcY2,
- Draw::Framebuffer *dest, float destX1, float destY1, float destX2, float destY2,
- bool linearFilter,
- int scaleFactor, // usually unused, except for swizzle...
- Draw2DPipeline *pipeline, const char *tag);
-
void CopyFramebufferForColorTexture(VirtualFramebuffer *dst, VirtualFramebuffer *src, int flags);
void EstimateDrawingSize(u32 fb_address, int fb_stride, GEBufferFormat fb_format, int viewport_width, int viewport_height, int region_width, int region_height, int scissor_width, int scissor_height, int &drawing_width, int &drawing_height);
@@ -562,6 +571,7 @@ protected:
// Draw2D pipelines
Draw2DPipeline *draw2DPipelineColor_ = nullptr;
+ Draw2DPipeline *draw2DPipelineColorRect2Lin_ = nullptr;
Draw2DPipeline *draw2DPipelineDepth_ = nullptr;
Draw2DPipeline *draw2DPipeline565ToDepth_ = nullptr;
Draw2DPipeline *draw2DPipeline565ToDepthDeswizzle_ = nullptr;
diff --git a/GPU/Common/GPUDebugInterface.h b/GPU/Common/GPUDebugInterface.h
index 09254ddf53..f584dfd576 100644
--- a/GPU/Common/GPUDebugInterface.h
+++ b/GPU/Common/GPUDebugInterface.h
@@ -213,6 +213,9 @@ public:
virtual void SetCmdValue(u32 op) = 0;
virtual void DispatchFlush() = 0;
+ virtual uint32_t SetAddrTranslation(uint32_t value) = 0;
+ virtual uint32_t GetAddrTranslation() = 0;
+
virtual bool GetCurrentSimpleVertices(int count, std::vector &vertices, std::vector &indices) {
return false;
}
diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp
index fb90850cb9..2b8bb84dc1 100644
--- a/GPU/Common/GPUStateUtils.cpp
+++ b/GPU/Common/GPUStateUtils.cpp
@@ -231,6 +231,7 @@ StencilValueType ReplaceAlphaWithStencilType() {
case GE_FORMAT_8888:
case GE_FORMAT_INVALID:
case GE_FORMAT_DEPTH16:
+ case GE_FORMAT_CLUT8:
switch (gstate.getStencilOpZPass()) {
case GE_STENCILOP_REPLACE:
// TODO: Could detect zero here and force ZERO - less uniform updates?
@@ -859,66 +860,63 @@ static inline bool blendColorSimilar(uint32_t a, uint32_t b, int margin = 25) {
static bool SimulateLogicOpIfNeeded(BlendFactor &srcBlend, BlendFactor &dstBlend, BlendEq &blendEq) {
// Note: our shader solution applies logic ops BEFORE blending, not correctly after.
// This is however fine for the most common ones, like CLEAR/NOOP/SET, etc.
- if (!gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP)) {
- if (gstate.isLogicOpEnabled()) {
- switch (gstate.getLogicOp()) {
- case GE_LOGIC_CLEAR:
- srcBlend = BlendFactor::ZERO;
- dstBlend = BlendFactor::ZERO;
- blendEq = BlendEq::ADD;
- return true;
- case GE_LOGIC_AND:
- case GE_LOGIC_AND_REVERSE:
- WARN_LOG_REPORT_ONCE(d3dLogicOpAnd, G3D, "Unsupported AND logic op: %x", gstate.getLogicOp());
- break;
- case GE_LOGIC_COPY:
- // This is the same as off.
- break;
- case GE_LOGIC_COPY_INVERTED:
- // Handled in the shader.
- break;
- case GE_LOGIC_AND_INVERTED:
- case GE_LOGIC_NOR:
- case GE_LOGIC_NAND:
- case GE_LOGIC_EQUIV:
- // Handled in the shader.
- WARN_LOG_REPORT_ONCE(d3dLogicOpAndInverted, G3D, "Attempted invert for logic op: %x", gstate.getLogicOp());
- break;
- case GE_LOGIC_INVERTED:
- srcBlend = BlendFactor::ONE;
- dstBlend = BlendFactor::ONE;
- blendEq = BlendEq::SUBTRACT;
- WARN_LOG_REPORT_ONCE(d3dLogicOpInverted, G3D, "Attempted inverse for logic op: %x", gstate.getLogicOp());
- return true;
- case GE_LOGIC_NOOP:
- srcBlend = BlendFactor::ZERO;
- dstBlend = BlendFactor::ONE;
- blendEq = BlendEq::ADD;
- return true;
- case GE_LOGIC_XOR:
- WARN_LOG_REPORT_ONCE(d3dLogicOpOrXor, G3D, "Unsupported XOR logic op: %x", gstate.getLogicOp());
- break;
- case GE_LOGIC_OR:
- case GE_LOGIC_OR_INVERTED:
- // Inverted in shader.
- srcBlend = BlendFactor::ONE;
- dstBlend = BlendFactor::ONE;
- blendEq = BlendEq::ADD;
- WARN_LOG_REPORT_ONCE(d3dLogicOpOr, G3D, "Attempted or for logic op: %x", gstate.getLogicOp());
- return true;
- case GE_LOGIC_OR_REVERSE:
- WARN_LOG_REPORT_ONCE(d3dLogicOpOrReverse, G3D, "Unsupported OR REVERSE logic op: %x", gstate.getLogicOp());
- break;
- case GE_LOGIC_SET:
- srcBlend = BlendFactor::ONE;
- dstBlend = BlendFactor::ONE;
- blendEq = BlendEq::ADD;
- WARN_LOG_REPORT_ONCE(d3dLogicOpSet, G3D, "Attempted set for logic op: %x", gstate.getLogicOp());
- return true;
- }
+ if (!gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP) && gstate.isLogicOpEnabled()) {
+ switch (gstate.getLogicOp()) {
+ case GE_LOGIC_CLEAR:
+ srcBlend = BlendFactor::ZERO;
+ dstBlend = BlendFactor::ZERO;
+ blendEq = BlendEq::ADD;
+ return true;
+ case GE_LOGIC_AND:
+ case GE_LOGIC_AND_REVERSE:
+ WARN_LOG_REPORT_ONCE(d3dLogicOpAnd, G3D, "Unsupported AND logic op: %x", gstate.getLogicOp());
+ break;
+ case GE_LOGIC_COPY:
+ // This is the same as off.
+ break;
+ case GE_LOGIC_COPY_INVERTED:
+ // Handled in the shader.
+ break;
+ case GE_LOGIC_AND_INVERTED:
+ case GE_LOGIC_NOR:
+ case GE_LOGIC_NAND:
+ case GE_LOGIC_EQUIV:
+ // Handled in the shader.
+ WARN_LOG_REPORT_ONCE(d3dLogicOpAndInverted, G3D, "Attempted invert for logic op: %x", gstate.getLogicOp());
+ break;
+ case GE_LOGIC_INVERTED:
+ srcBlend = BlendFactor::ONE;
+ dstBlend = BlendFactor::ONE;
+ blendEq = BlendEq::SUBTRACT;
+ WARN_LOG_REPORT_ONCE(d3dLogicOpInverted, G3D, "Attempted inverse for logic op: %x", gstate.getLogicOp());
+ return true;
+ case GE_LOGIC_NOOP:
+ srcBlend = BlendFactor::ZERO;
+ dstBlend = BlendFactor::ONE;
+ blendEq = BlendEq::ADD;
+ return true;
+ case GE_LOGIC_XOR:
+ WARN_LOG_REPORT_ONCE(d3dLogicOpOrXor, G3D, "Unsupported XOR logic op: %x", gstate.getLogicOp());
+ break;
+ case GE_LOGIC_OR:
+ case GE_LOGIC_OR_INVERTED:
+ // Inverted in shader.
+ srcBlend = BlendFactor::ONE;
+ dstBlend = BlendFactor::ONE;
+ blendEq = BlendEq::ADD;
+ WARN_LOG_REPORT_ONCE(d3dLogicOpOr, G3D, "Attempted or for logic op: %x", gstate.getLogicOp());
+ return true;
+ case GE_LOGIC_OR_REVERSE:
+ WARN_LOG_REPORT_ONCE(d3dLogicOpOrReverse, G3D, "Unsupported OR REVERSE logic op: %x", gstate.getLogicOp());
+ break;
+ case GE_LOGIC_SET:
+ srcBlend = BlendFactor::ONE;
+ dstBlend = BlendFactor::ONE;
+ blendEq = BlendEq::ADD;
+ WARN_LOG_REPORT_ONCE(d3dLogicOpSet, G3D, "Attempted set for logic op: %x", gstate.getLogicOp());
+ return true;
}
}
-
return false;
}
@@ -1080,6 +1078,12 @@ static void ConvertBlendState(GenericBlendState &blendState, bool forceReplaceBl
case REPLACE_BLEND_NO:
// We may still want to do something about stencil -> alpha.
ApplyStencilReplaceAndLogicOpIgnoreBlend(replaceAlphaWithStencil, blendState);
+
+ if (forceReplaceBlend) {
+ // If this is true, the logic and mask replacements will be applied, at least. In that case,
+ // we should not apply any logic op simulation.
+ blendState.simulateLogicOpType = LOGICOPTYPE_NORMAL;
+ }
return;
case REPLACE_BLEND_BLUE_TO_ALPHA:
diff --git a/GPU/Common/GeometryShaderGenerator.cpp b/GPU/Common/GeometryShaderGenerator.cpp
new file mode 100644
index 0000000000..a814a345da
--- /dev/null
+++ b/GPU/Common/GeometryShaderGenerator.cpp
@@ -0,0 +1,310 @@
+// Copyright (c) 2012- PPSSPP 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 git repository and contact information can be found at
+// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
+
+#include
+#include
+#include
+
+#include "Common/StringUtils.h"
+#include "Common/GPU/OpenGL/GLFeatures.h"
+#include "Common/GPU/ShaderWriter.h"
+#include "Common/GPU/thin3d.h"
+#include "Core/Config.h"
+#include "GPU/ge_constants.h"
+#include "GPU/GPUState.h"
+#include "GPU/Common/ShaderId.h"
+#include "GPU/Common/ShaderUniforms.h"
+#include "GPU/Common/GeometryShaderGenerator.h"
+
+#undef WRITE
+
+#define WRITE(p, ...) p.F(__VA_ARGS__)
+
+// TODO: Could support VK_NV_geometry_shader_passthrough, though the hardware that supports
+// it is already pretty fast at geometry shaders..
+
+
+bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLanguageDesc &compat, const Draw::Bugs bugs, std::string *errorString) {
+ std::vector gl_exts;
+ if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) {
+ if (gl_extensions.EXT_gpu_shader4) {
+ gl_exts.push_back("#extension GL_EXT_gpu_shader4 : enable");
+ }
+ }
+ bool vertexRangeCulling = !id.Bit(GS_BIT_CURVE);
+ bool clipClampedDepth = gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP);
+
+ ShaderWriter p(buffer, compat, ShaderStage::Geometry, gl_exts.data(), gl_exts.size());
+ p.C("layout(triangles) in;\n");
+ if (clipClampedDepth && vertexRangeCulling && !gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) {
+ p.C("layout(triangle_strip, max_vertices = 12) out;\n");
+ } else {
+ p.C("layout(triangle_strip, max_vertices = 6) out;\n");
+ }
+
+ if (compat.shaderLanguage == GLSL_VULKAN) {
+ WRITE(p, "\n");
+ WRITE(p, "layout (std140, set = 0, binding = 3) uniform baseVars {\n%s};\n", ub_baseStr);
+ } else if (compat.shaderLanguage == HLSL_D3D11) {
+ WRITE(p, "cbuffer base : register(b0) {\n%s};\n", ub_baseStr);
+ }
+
+ std::vector varyings, outVaryings;
+
+ if (id.Bit(GS_BIT_DO_TEXTURE)) {
+ varyings.push_back(VaryingDef{ "vec3", "v_texcoord", Draw::SEM_TEXCOORD0, 0, "highp" });
+ outVaryings.push_back(VaryingDef{ "vec3", "v_texcoordOut", Draw::SEM_TEXCOORD0, 0, "highp" });
+ }
+ varyings.push_back(VaryingDef{ "vec4", "v_color0", Draw::SEM_COLOR0, 1, "lowp" });
+ outVaryings.push_back(VaryingDef{ "vec4", "v_color0Out", Draw::SEM_COLOR0, 1, "lowp" });
+ if (id.Bit(GS_BIT_LMODE)) {
+ varyings.push_back(VaryingDef{ "vec3", "v_color1", Draw::SEM_COLOR1, 2, "lowp" });
+ outVaryings.push_back(VaryingDef{ "vec3", "v_color1Out", Draw::SEM_COLOR1, 2, "lowp" });
+ }
+ varyings.push_back(VaryingDef{ "float", "v_fogdepth", Draw::SEM_TEXCOORD1, 3, "highp" });
+ outVaryings.push_back(VaryingDef{ "float", "v_fogdepthOut", Draw::SEM_TEXCOORD1, 3, "highp" });
+
+ p.BeginGSMain(varyings, outVaryings);
+
+ // Apply culling.
+ if (vertexRangeCulling) {
+ p.C(" bool anyInside = false;\n");
+ }
+ // And apply manual clipping if necessary.
+ if (!gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) {
+ p.C(" float clip0[3];\n");
+ if (clipClampedDepth) {
+ p.C(" float clip1[3];\n");
+ }
+ }
+
+ p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster?
+ p.C(" vec4 outPos = gl_in[i].gl_Position;\n");
+ p.C(" vec3 projPos = outPos.xyz / outPos.w;\n");
+
+ if (vertexRangeCulling) {
+ p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n");
+ // Vertex range culling doesn't happen when Z clips, note sign of w is important.
+ p.C(" if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n");
+ const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y";
+ const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y";
+ p.F(" if ((%s) || (%s)) {\n", outMin, outMax);
+ p.C(" return;\n"); // Cull!
+ p.C(" }\n");
+ p.C(" }\n");
+ p.C(" if (u_cullRangeMin.w <= 0.0) {\n");
+ p.C(" if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n");
+ // When not clamping depth, cull the triangle of Z is outside the valid range (not based on clip Z.)
+ p.C(" return;\n");
+ p.C(" }\n");
+ p.C(" } else {\n");
+ p.C(" if (projPos.z >= u_cullRangeMin.z) { anyInside = true; }\n");
+ p.C(" if (projPos.z <= u_cullRangeMax.z) { anyInside = true; }\n");
+ p.C(" }\n");
+ }
+
+ if (!gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) {
+ // This is basically the same value as gl_ClipDistance would take, z + w.
+ if (vertexRangeCulling) {
+ p.C(" clip0[i] = projZ * outPos.w + outPos.w;\n");
+ } else {
+ // Let's not complicate the code overly for this case. We'll clipClampedDepth.
+ p.C(" clip0[i] = 0.0;\n");
+ }
+
+ // This one does happen for rectangles.
+ if (clipClampedDepth) {
+ if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) {
+ // On OpenGL/GLES, these values account for the -1 -> 1 range.
+ p.C(" if (u_depthRange.y - u_depthRange.x >= 1.0) {\n");
+ p.C(" clip1[i] = outPos.w + outPos.z;\n");
+ } else {
+ // Everywhere else, it's 0 -> 1, simpler.
+ p.C(" if (u_depthRange.y >= 1.0) {\n");
+ p.C(" clip1[i] = outPos.z;\n");
+ }
+ // This is similar, but for maxz when it's below 65535.0. -1/0 don't matter here.
+ p.C(" } else if (u_depthRange.x + u_depthRange.y <= 65534.0) {\n");
+ p.C(" clip1[i] = outPos.w - outPos.z;\n");
+ p.C(" } else {\n");
+ p.C(" clip1[i] = 0.0;\n");
+ p.C(" }\n");
+ }
+ }
+
+ p.C(" } // for\n");
+
+ // Cull any triangle fully outside in the same direction when depth clamp enabled.
+ // Basically simulate cull distances.
+ if (vertexRangeCulling) {
+ p.C(" if (u_cullRangeMin.w > 0.0 && !anyInside) {\n");
+ p.C(" return;\n");
+ p.C(" }\n");
+ }
+
+ if (!gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) {
+ // Clipping against one half-space cuts a triangle (17/27), culls (7/27), or creates two triangles (3/27).
+ // We clip against two, so we can generate up to 4 triangles, a polygon with 6 points.
+ p.C(" int indices[6];\n");
+ p.C(" float factors[6];\n");
+ p.C(" int ind = 0;\n");
+
+ // Pass 1 - clip against first half-space.
+ p.C(" for (int i = 0; i < 3; i++) {\n");
+ // First, use this vertex if it doesn't need clipping.
+ p.C(" if (clip0[i] >= 0.0) {\n");
+ p.C(" indices[ind] = i;\n");
+ p.C(" factors[ind] = 0.0;\n");
+ p.C(" ind++;\n");
+ p.C(" }\n");
+
+ // Next, we generate an interpolated vertex if signs differ.
+ p.C(" int inext = i == 2 ? 0 : i + 1;\n");
+ p.C(" if (clip0[i] * clip0[inext] < 0.0) {\n");
+ p.C(" float t = clip0[i] < 0.0 ? clip0[i] / (clip0[i] - clip0[inext]) : 1.0 - (clip0[inext] / (clip0[inext] - clip0[i]));\n");
+ p.C(" indices[ind] = i;\n");
+ p.C(" factors[ind] = t;\n");
+ p.C(" ind++;\n");
+ p.C(" }\n");
+
+ p.C(" }\n");
+
+ // Pass 2 - further clip against clamped Z.
+ if (clipClampedDepth) {
+ p.C(" int count0 = ind;\n");
+ p.C(" int indices1[6];\n");
+ p.C(" float factors1[6];\n");
+ p.C(" ind = 0;\n");
+
+ // Let's start by interpolating the clip values.
+ p.C(" float clip1after[4];\n");
+ p.C(" for (int i = 0; i < count0; i++) {\n");
+ p.C(" int idx = indices[i];\n");
+ p.C(" float factor = factors[i];\n");
+ p.C(" int next = idx == 2 ? 0 : idx + 1;\n");
+ p.C(" clip1after[i] = mix(clip1[idx], clip1[next], factor);\n");
+ p.C(" }\n");
+
+ // Alright, now time to clip, again.
+ p.C(" for (int i = 0; i < count0; i++) {\n");
+ // First, use this vertex if it doesn't need clipping.
+ p.C(" if (clip1after[i] >= 0.0) {\n");
+ p.C(" indices1[ind] = i;\n");
+ p.C(" factors1[ind] = 0.0;\n");
+ p.C(" ind++;\n");
+ p.C(" }\n");
+
+ // Next, we generate an interpolated vertex if signs differ.
+ p.C(" int inext = i == count0 - 1 ? 0 : i + 1;\n");
+ p.C(" if (clip1after[i] * clip1after[inext] < 0.0) {\n");
+ p.C(" float t = clip1after[i] < 0.0 ? clip1after[i] / (clip1after[i] - clip1after[inext]) : 1.0 - (clip1after[inext] / (clip1after[inext] - clip1after[i]));\n");
+ p.C(" indices1[ind] = i;\n");
+ p.C(" factors1[ind] = t;\n");
+ p.C(" ind++;\n");
+ p.C(" }\n");
+
+ p.C(" }\n");
+ }
+
+ p.C(" if (ind < 3) {\n");
+ p.C(" return;\n");
+ p.C(" }\n");
+
+ p.C(" int idx;\n");
+ p.C(" int next;\n");
+ p.C(" float factor;\n");
+
+ auto emitIndex = [&](const char *which) {
+ if (clipClampedDepth) {
+ // We have to interpolate between four vertices.
+ p.F(" idx = indices1[%s];\n", which);
+ p.F(" factor = factors1[%s];\n", which);
+ p.C(" next = idx == count0 - 1 ? 0 : idx + 1;\n");
+ p.C(" gl_Position = mix(mix(gl_in[indices[idx]].gl_Position, gl_in[(indices[idx] + 1) % 3].gl_Position, factors[idx]), mix(gl_in[indices[next]].gl_Position, gl_in[(indices[next] + 1) % 3].gl_Position, factors[next]), factor);\n");
+ for (size_t i = 0; i < varyings.size(); i++) {
+ const VaryingDef &in = varyings[i];
+ const VaryingDef &out = outVaryings[i];
+ p.F(" %s = mix(mix(%s[indices[idx]], %s[(indices[idx] + 1) % 3], factors[idx]), mix(%s[indices[next]], %s[(indices[next] + 1) % 3], factors[next]), factor);\n", out.name, in.name, in.name, in.name, in.name);
+ }
+ } else {
+ p.F(" idx = indices[%s];\n", which);
+ p.F(" factor = factors[%s];\n", which);
+ p.C(" next = idx == 2 ? 0 : idx + 1;\n");
+ p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n");
+ for (size_t i = 0; i < varyings.size(); i++) {
+ const VaryingDef &in = varyings[i];
+ const VaryingDef &out = outVaryings[i];
+ p.F(" %s = mix(%s[idx], %s[next], factor);\n", out.name, in.name, in.name);
+ }
+ }
+ p.C(" EmitVertex();\n");
+ };
+
+ // Alright, time to actually emit the first triangle.
+ p.C(" for (int i = 0; i < 3; i++) {\n");
+ emitIndex("i");
+ p.C(" }\n");
+
+ // Did we end up with additional triangles? We'll do three points each for the rest.
+ p.C(" for (int i = 3; i < ind; i++) {\n");
+ p.C(" EndPrimitive();\n");
+
+ // Point one, always index zero.
+ emitIndex("0");
+
+ // After that, one less than i (basically a triangle fan.)
+ emitIndex("(i - 1)");
+
+ // And the new vertex itself.
+ emitIndex("i");
+
+ p.C(" }\n");
+ } else {
+ const char *clipSuffix0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]";
+ const char *clipSuffix1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]";
+
+ p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster?
+ p.C(" vec4 outPos = gl_in[i].gl_Position;\n");
+ p.C(" vec3 projPos = outPos.xyz / outPos.w;\n");
+ p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n");
+ if (clipClampedDepth) {
+ // Copy the clip distance from the vertex shader.
+ p.F(" gl_ClipDistance%s = gl_in[i].gl_ClipDistance%s;\n", clipSuffix0, clipSuffix0);
+ p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix1);
+ } else {
+ // We shouldn't need to worry about rectangles-as-triangles here, since we don't use geometry shaders for that.
+ p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix0);
+ }
+ p.C(" gl_Position = outPos;\n");
+ if (gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) {
+ }
+
+ for (size_t i = 0; i < varyings.size(); i++) {
+ const VaryingDef &in = varyings[i];
+ const VaryingDef &out = outVaryings[i];
+ p.F(" %s = %s[i];\n", out.name, in.name);
+ }
+ // Debug - null the red channel
+ //p.C(" if (i == 0) v_color0Out.x = 0.0;\n");
+ p.C(" EmitVertex();\n");
+ p.C(" }\n");
+ }
+
+ p.EndGSMain();
+
+ return true;
+}
diff --git a/GPU/Common/GeometryShaderGenerator.h b/GPU/Common/GeometryShaderGenerator.h
new file mode 100644
index 0000000000..9848ffde18
--- /dev/null
+++ b/GPU/Common/GeometryShaderGenerator.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "GPU/Common/ShaderId.h"
+
+bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLanguageDesc &compat, const Draw::Bugs bugs, std::string *errorString);
diff --git a/GPU/Common/PostShader.cpp b/GPU/Common/PostShader.cpp
index ed86397521..2d8529f356 100644
--- a/GPU/Common/PostShader.cpp
+++ b/GPU/Common/PostShader.cpp
@@ -55,7 +55,7 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct
off.name = "Off";
off.section = "Off";
for (size_t i = 0; i < ARRAY_SIZE(off.settings); ++i) {
- off.settings[i].name = "";
+ off.settings[i].name.clear();
off.settings[i].value = 0.0f;
off.settings[i].minValue = -1.0f;
off.settings[i].maxValue = 1.0f;
@@ -174,7 +174,7 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct
section.Get("UsePreviousFrame", &info.usePreviousFrame, false);
if (info.parent == "Off")
- info.parent = "";
+ info.parent.clear();
for (size_t i = 0; i < ARRAY_SIZE(info.settings); ++i) {
auto &setting = info.settings[i];
@@ -187,7 +187,7 @@ void LoadPostShaderInfo(Draw::DrawContext *draw, const std::vector &direct
// Populate the default setting value.
std::string section = StringFromFormat("%sSettingValue%d", info.section.c_str(), i + 1);
if (!setting.name.empty() && g_Config.mPostShaderSetting.find(section) == g_Config.mPostShaderSetting.end()) {
- g_Config.mPostShaderSetting.insert(std::pair(section, setting.value));
+ g_Config.mPostShaderSetting.emplace(section, setting.value);
}
}
diff --git a/GPU/Common/ShaderCommon.h b/GPU/Common/ShaderCommon.h
index 1a69e5c387..63e3116014 100644
--- a/GPU/Common/ShaderCommon.h
+++ b/GPU/Common/ShaderCommon.h
@@ -88,12 +88,13 @@ enum : uint64_t {
DIRTY_COLORWRITEMASK = 1ULL << 36,
DIRTY_MIPBIAS = 1ULL << 37,
+ DIRTY_LIGHT_CONTROL = 1ULL << 38,
- // space for 4 more uniform dirty flags. Remember to update DIRTY_ALL_UNIFORMS.
+ // space for 1 more uniform dirty flags. Remember to update DIRTY_ALL_UNIFORMS.
DIRTY_BONE_UNIFORMS = 0xFF000000ULL,
- DIRTY_ALL_UNIFORMS = 0x3FFFFFFFFFULL,
+ DIRTY_ALL_UNIFORMS = 0x7FFFFFFFFFULL,
DIRTY_ALL_LIGHTS = DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3,
// Other dirty elements that aren't uniforms!
@@ -108,6 +109,13 @@ enum : uint64_t {
DIRTY_VIEWPORTSCISSOR_STATE = 1ULL << 46,
DIRTY_VERTEXSHADER_STATE = 1ULL << 47,
DIRTY_FRAGMENTSHADER_STATE = 1ULL << 48,
+ DIRTY_GEOMETRYSHADER_STATE = 1ULL << 49,
+
+ // Everything that's not uniforms. Use this after using thin3d.
+ // TODO: Should we also add DIRTY_FRAMEBUF here? It kinda generally takes care of itself.
+ DIRTY_ALL_RENDER_STATE = DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS,
+
+ // Note that the top 8 bits (54-63) cannot be dirtied through the commonCommandTable due to packing of other flags.
DIRTY_ALL = 0xFFFFFFFFFFFFFFFF
};
diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp
index f873024c39..94ade2fd26 100644
--- a/GPU/Common/ShaderId.cpp
+++ b/GPU/Common/ShaderId.cpp
@@ -22,7 +22,6 @@ std::string VertexShaderDesc(const VShaderID &id) {
if (id.Bit(VS_BIT_HAS_TEXCOORD)) desc << "T ";
if (id.Bit(VS_BIT_HAS_NORMAL)) desc << "N ";
if (id.Bit(VS_BIT_LMODE)) desc << "LM ";
- if (id.Bit(VS_BIT_ENABLE_FOG)) desc << "Fog ";
if (id.Bit(VS_BIT_NORM_REVERSE)) desc << "RevN ";
if (id.Bit(VS_BIT_DO_TEXTURE)) desc << "Tex ";
int uvgMode = id.Bits(VS_BIT_UVGEN_MODE, 2);
@@ -41,6 +40,9 @@ std::string VertexShaderDesc(const VShaderID &id) {
if (id.Bit(VS_BIT_LIGHTING_ENABLE)) {
desc << "Light: ";
}
+ if (id.Bit(VS_BIT_LIGHT_UBERSHADER)) {
+ desc << "LightUberShader ";
+ }
for (int i = 0; i < 4; i++) {
bool enabled = id.Bit(VS_BIT_LIGHT0_ENABLE + i) && id.Bit(VS_BIT_LIGHTING_ENABLE);
if (enabled || (uvgMode == GE_TEXMAP_ENVIRONMENT_MAP && (ls0 == i || ls1 == i))) {
@@ -80,14 +82,12 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform,
_assert_(hasNormal);
}
- bool enableFog = gstate.isFogEnabled() && !isModeThrough && !gstate.isModeClear();
bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough && !gstate.isModeClear();
bool vertexRangeCulling = gstate_c.Supports(GPU_SUPPORTS_VS_RANGE_CULLING) &&
!isModeThrough && gstate_c.submitType == SubmitType::DRAW; // neither hw nor sw spline/bezier. See #11692
VShaderID id;
id.SetBit(VS_BIT_LMODE, lmode);
- id.SetBit(VS_BIT_ENABLE_FOG, enableFog);
id.SetBit(VS_BIT_IS_THROUGH, isModeThrough);
id.SetBit(VS_BIT_HAS_COLOR, hasColor);
id.SetBit(VS_BIT_VERTEX_RANGE_CULLING, vertexRangeCulling);
@@ -123,15 +123,19 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform,
if (gstate.isLightingEnabled()) {
// doShadeMapping is stored as UVGenMode, and light type doesn't matter for shade mapping.
- id.SetBits(VS_BIT_MATERIAL_UPDATE, 3, gstate.getMaterialUpdate());
id.SetBit(VS_BIT_LIGHTING_ENABLE);
- // Light bits
- for (int i = 0; i < 4; i++) {
- bool chanEnabled = gstate.isLightChanEnabled(i) != 0;
- id.SetBit(VS_BIT_LIGHT0_ENABLE + i, chanEnabled);
- if (chanEnabled) {
- id.SetBits(VS_BIT_LIGHT0_COMP + 4 * i, 2, gstate.getLightComputation(i));
- id.SetBits(VS_BIT_LIGHT0_TYPE + 4 * i, 2, gstate.getLightType(i));
+ if (gstate_c.Supports(GPU_USE_LIGHT_UBERSHADER)) {
+ id.SetBit(VS_BIT_LIGHT_UBERSHADER);
+ } else {
+ id.SetBits(VS_BIT_MATERIAL_UPDATE, 3, gstate.getMaterialUpdate());
+ // Light bits
+ for (int i = 0; i < 4; i++) {
+ bool chanEnabled = gstate.isLightChanEnabled(i) != 0;
+ id.SetBit(VS_BIT_LIGHT0_ENABLE + i, chanEnabled);
+ if (chanEnabled) {
+ id.SetBits(VS_BIT_LIGHT0_COMP + 4 * i, 2, gstate.getLightComputation(i));
+ id.SetBits(VS_BIT_LIGHT0_TYPE + 4 * i, 2, gstate.getLightType(i));
+ }
}
}
}
@@ -180,7 +184,7 @@ std::string FragmentShaderDesc(const FShaderID &id) {
if (id.Bit(FS_BIT_COLOR_DOUBLE)) desc << "2x ";
if (id.Bit(FS_BIT_FLATSHADE)) desc << "Flat ";
if (id.Bit(FS_BIT_BGRA_TEXTURE)) desc << "BGRA ";
- switch ((ShaderDepalMode)id.Bit(FS_BIT_SHADER_DEPAL_MODE)) {
+ switch ((ShaderDepalMode)id.Bits(FS_BIT_SHADER_DEPAL_MODE, 2)) {
case ShaderDepalMode::OFF: break;
case ShaderDepalMode::NORMAL: desc << "Depal "; break;
case ShaderDepalMode::SMOOTHED: desc << "SmoothDepal "; break;
@@ -223,7 +227,7 @@ std::string FragmentShaderDesc(const FShaderID &id) {
case STENCIL_VALUE_INCR_4: desc << "StenIncr4 "; break;
case STENCIL_VALUE_INCR_8: desc << "StenIncr8 "; break;
case STENCIL_VALUE_DECR_4: desc << "StenDecr4 "; break;
- case STENCIL_VALUE_DECR_8: desc << "StenDecr4 "; break;
+ case STENCIL_VALUE_DECR_8: desc << "StenDecr8 "; break;
default: desc << "StenUnknown "; break;
}
} else if (id.Bit(FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE)) {
@@ -244,6 +248,9 @@ std::string FragmentShaderDesc(const FShaderID &id) {
else if (id.Bit(FS_BIT_ALPHA_TEST)) desc << "AlphaTest " << alphaTestFuncs[id.Bits(FS_BIT_ALPHA_TEST_FUNC, 3)] << " ";
if (id.Bit(FS_BIT_COLOR_AGAINST_ZERO)) desc << "ColorTest0 " << alphaTestFuncs[id.Bits(FS_BIT_COLOR_TEST_FUNC, 2)] << " "; // first 4 match;
else if (id.Bit(FS_BIT_COLOR_TEST)) desc << "ColorTest " << alphaTestFuncs[id.Bits(FS_BIT_COLOR_TEST_FUNC, 2)] << " "; // first 4 match
+ if (id.Bit(FS_BIT_TEST_DISCARD_TO_ZERO)) desc << "TestDiscardToZero ";
+ if (id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL)) desc << "StencilDiscardWorkaround ";
+ if (id.Bits(FS_BIT_REPLACE_LOGIC_OP, 4) != GE_LOGIC_COPY) desc << "ReplaceLogic ";
return desc.str();
}
@@ -349,16 +356,57 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip
id.SetBit(FS_BIT_COLOR_WRITEMASK, colorWriteMask);
- if (g_Config.bVendorBugChecksEnabled) {
- if (bugs.Has(Draw::Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL)) {
- id.SetBit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL, !IsStencilTestOutputDisabled() && !gstate.isDepthWriteEnabled());
- } else if (bugs.Has(Draw::Bugs::MALI_STENCIL_DISCARD_BUG) && PSP_CoreParameter().compat.flags().MaliDepthStencilBugWorkaround) {
- // Very similar driver bug to the Adreno one, with the same workaround (though might look into if there are cheaper ones!)
- // Keeping the conditions separate since it can probably be made tighter.
- id.SetBit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL, !IsStencilTestOutputDisabled() && (!gstate.isDepthTestEnabled() || !gstate.isDepthWriteEnabled()));
+ if (g_Config.bVendorBugChecksEnabled && bugs.Has(Draw::Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL)) {
+ bool stencilWithoutDepth = !IsStencilTestOutputDisabled() && (!gstate.isDepthTestEnabled() || !gstate.isDepthWriteEnabled());
+ if (stencilWithoutDepth) {
+ id.SetBit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL, stencilWithoutDepth);
}
}
}
*id_out = id;
}
+
+std::string GeometryShaderDesc(const GShaderID &id) {
+ std::stringstream desc;
+ desc << StringFromFormat("%08x:%08x ", id.d[1], id.d[0]);
+ if (id.Bit(GS_BIT_ENABLED)) desc << "ENABLED ";
+ if (id.Bit(GS_BIT_DO_TEXTURE)) desc << "TEX ";
+ if (id.Bit(GS_BIT_LMODE)) desc << "LMODE ";
+ return desc.str();
+}
+
+void ComputeGeometryShaderID(GShaderID *id_out, const Draw::Bugs &bugs, int prim) {
+ GShaderID id;
+
+ bool isModeThrough = gstate.isModeThrough();
+ bool isCurve = gstate_c.submitType != SubmitType::DRAW;
+ bool isTriangle = prim == GE_PRIM_TRIANGLES || prim == GE_PRIM_TRIANGLE_FAN || prim == GE_PRIM_TRIANGLE_STRIP;
+
+ bool vertexRangeCulling = !isCurve;
+ bool clipClampedDepth = gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && !gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE);
+
+ // If we're not using GS culling, return a zero ID.
+ // Also, only use this for triangle primitives.
+ if ((!vertexRangeCulling && !clipClampedDepth) || isModeThrough || !isTriangle || !gstate_c.Supports(GPU_SUPPORTS_GS_CULLING)) {
+ *id_out = id;
+ return;
+ }
+
+ id.SetBit(GS_BIT_ENABLED, true);
+ // Vertex range culling doesn't seem tno happen for spline/bezier, see #11692.
+ id.SetBit(GS_BIT_CURVE, isCurve);
+
+ if (gstate.isModeClear()) {
+ // No attribute bits.
+ } else {
+ bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough;
+
+ id.SetBit(GS_BIT_LMODE, lmode);
+ if (gstate.isTextureMapEnabled()) {
+ id.SetBit(GS_BIT_DO_TEXTURE);
+ }
+ }
+
+ *id_out = id;
+}
diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h
index 999faf2e44..560b8a873a 100644
--- a/GPU/Common/ShaderId.h
+++ b/GPU/Common/ShaderId.h
@@ -12,7 +12,6 @@
enum VShaderBit : uint8_t {
VS_BIT_LMODE = 0,
VS_BIT_IS_THROUGH = 1,
- VS_BIT_ENABLE_FOG = 2,
VS_BIT_HAS_COLOR = 3,
VS_BIT_DO_TEXTURE = 4,
VS_BIT_VERTEX_RANGE_CULLING = 5,
@@ -33,7 +32,11 @@ enum VShaderBit : uint8_t {
VS_BIT_BONES = 22, // 3 should be enough, not 8
// 25 - 29 are free.
VS_BIT_ENABLE_BONES = 30,
- // 31 is free.
+
+ // If this is set along with LIGHTING_ENABLE, all other lighting bits below
+ // are passed to the shader directly instead.
+ VS_BIT_LIGHT_UBERSHADER = 31,
+
VS_BIT_LIGHT0_COMP = 32, // 2 bits
VS_BIT_LIGHT0_TYPE = 34, // 2 bits
VS_BIT_LIGHT1_COMP = 36, // 2 bits
@@ -101,6 +104,18 @@ static inline FShaderBit operator +(FShaderBit bit, int i) {
return FShaderBit((int)bit + i);
}
+// Some of these bits are straight from FShaderBit, since they essentially enable attributes directly.
+enum GShaderBit : uint8_t {
+ GS_BIT_ENABLED = 0, // If not set, we don't use a geo shader.
+ GS_BIT_DO_TEXTURE = 1, // presence of texcoords
+ GS_BIT_LMODE = 2, // presence of specular color (regular color always present)
+ GS_BIT_CURVE = 3, // curve, which means don't do range culling.
+};
+
+static inline GShaderBit operator +(GShaderBit bit, int i) {
+ return GShaderBit((int)bit + i);
+}
+
struct ShaderID {
ShaderID() {
clear();
@@ -115,6 +130,13 @@ struct ShaderID {
d[i] = 0xFFFFFFFF;
}
}
+ bool is_invalid() const {
+ for (size_t i = 0; i < ARRAY_SIZE(d); i++) {
+ if (d[i] != 0xFFFFFFFF)
+ return false;
+ }
+ return true;
+ }
uint32_t d[2];
bool operator < (const ShaderID &other) const {
@@ -222,6 +244,31 @@ struct FShaderID : ShaderID {
}
};
+struct GShaderID : ShaderID {
+ GShaderID() : ShaderID() {
+ }
+
+ explicit GShaderID(ShaderID &src) {
+ memcpy(d, src.d, sizeof(d));
+ }
+
+ bool Bit(GShaderBit bit) const {
+ return ShaderID::Bit((int)bit);
+ }
+
+ int Bits(GShaderBit bit, int count) const {
+ return ShaderID::Bits((int)bit, count);
+ }
+
+ void SetBit(GShaderBit bit, bool value = true) {
+ ShaderID::SetBit((int)bit, value);
+ }
+
+ void SetBits(GShaderBit bit, int count, int value) {
+ ShaderID::SetBits((int)bit, count, value);
+ }
+};
+
namespace Draw {
class Bugs;
}
@@ -234,3 +281,6 @@ std::string VertexShaderDesc(const VShaderID &id);
struct ComputedPipelineState;
void ComputeFragmentShaderID(FShaderID *id, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs);
std::string FragmentShaderDesc(const FShaderID &id);
+
+void ComputeGeometryShaderID(GShaderID *id, const Draw::Bugs &bugs, int prim);
+std::string GeometryShaderDesc(const GShaderID &id);
diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp
index c3480e6fef..b276f7f094 100644
--- a/GPU/Common/ShaderUniforms.cpp
+++ b/GPU/Common/ShaderUniforms.cpp
@@ -74,20 +74,20 @@ void CalcCullRange(float minValues[4], float maxValues[4], bool flipViewport, bo
void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipViewport, bool useBufferedRendering) {
if (dirtyUniforms & DIRTY_TEXENV) {
- Uint8x3ToFloat4(ub->texEnvColor, gstate.texenvcolor);
+ Uint8x3ToFloat3(ub->texEnvColor, gstate.texenvcolor);
}
if (dirtyUniforms & DIRTY_ALPHACOLORREF) {
Uint8x3ToInt4_Alpha(ub->alphaColorRef, gstate.getColorTestRef(), gstate.getAlphaTestRef() & gstate.getAlphaTestMask());
}
if (dirtyUniforms & DIRTY_ALPHACOLORMASK) {
- Uint8x3ToInt4_Alpha(ub->colorTestMask, gstate.getColorTestMask(), gstate.getAlphaTestMask());
+ ub->colorTestMask = gstate.getColorTestMask() | (gstate.getAlphaTestMask() << 24);
}
if (dirtyUniforms & DIRTY_FOGCOLOR) {
Uint8x3ToFloat4(ub->fogColor, gstate.fogcolor);
}
if (dirtyUniforms & DIRTY_SHADERBLEND) {
- Uint8x3ToFloat4(ub->blendFixA, gstate.getFixA());
- Uint8x3ToFloat4(ub->blendFixB, gstate.getFixB());
+ Uint8x3ToFloat3(ub->blendFixA, gstate.getFixA());
+ Uint8x3ToFloat3(ub->blendFixB, gstate.getFixB());
}
if (dirtyUniforms & DIRTY_TEXCLAMP) {
const float invW = 1.0f / (float)gstate_c.curTextureWidth;
@@ -267,6 +267,29 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView
}
}
+uint32_t PackLightControlBits() {
+ // Bit organization
+ // Bottom 4 bits are enable bits for each light.
+ // Then, for each light, comes 2 bits for "comp" and 2 bits for "type".
+ // At the end, at bit 20, we put the three material update bits.
+
+ uint32_t lightControl = 0;
+ for (int i = 0; i < 4; i++) {
+ if (gstate.isLightChanEnabled(i)) {
+ lightControl |= 1 << i;
+ }
+
+ u32 computation = (u32)gstate.getLightComputation(i); // 2 bits
+ u32 type = (u32)gstate.getLightType(i); // 2 bits
+ lightControl |= computation << (4 + i * 4);
+ lightControl |= type << (4 + i * 4 + 2);
+ }
+
+ lightControl |= gstate.getMaterialUpdate() << 20;
+
+ return lightControl;
+}
+
void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms) {
// Lighting
if (dirtyUniforms & DIRTY_AMBIENT) {
@@ -279,7 +302,11 @@ void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms) {
Uint8x3ToFloat4_Alpha(ub->materialSpecular, gstate.materialspecular, std::max(0.0f, getFloat24(gstate.materialspecularcoef)));
}
if (dirtyUniforms & DIRTY_MATEMISSIVE) {
- Uint8x3ToFloat4(ub->materialEmissive, gstate.materialemissive);
+ // We're not touching the fourth f32 here, because we store an u32 of control bits in it.
+ Uint8x3ToFloat3(ub->materialEmissive, gstate.materialemissive);
+ }
+ if (dirtyUniforms & DIRTY_LIGHT_CONTROL) {
+ ub->lightControl = PackLightControlBits();
}
for (int i = 0; i < 4; i++) {
if (dirtyUniforms & (DIRTY_LIGHT0 << i)) {
diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h
index 691835a262..04b6437dff 100644
--- a/GPU/Common/ShaderUniforms.h
+++ b/GPU/Common/ShaderUniforms.h
@@ -13,14 +13,13 @@ enum : uint64_t {
DIRTY_ALPHACOLORMASK | DIRTY_SHADERBLEND | DIRTY_COLORWRITEMASK | DIRTY_UVSCALEOFFSET | DIRTY_TEXCLAMP | DIRTY_DEPTHRANGE | DIRTY_MATAMBIENTALPHA |
DIRTY_BEZIERSPLINE | DIRTY_DEPAL,
DIRTY_LIGHT_UNIFORMS =
- DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3 |
+ DIRTY_LIGHT_CONTROL | DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3 |
DIRTY_MATDIFFUSE | DIRTY_MATSPECULAR | DIRTY_MATEMISSIVE | DIRTY_AMBIENT,
};
-// TODO: Split into two structs, one for software transform and one for hardware transform, to save space.
-// Currently 512 bytes. Probably can't get to 256 (nVidia's UBO alignment).
+// Currently 480 bytes. Probably can't get to 256 (nVidia's UBO alignment, also common in other vendors).
// Every line here is a 4-float.
-struct UB_VS_FS_Base {
+struct alignas(16) UB_VS_FS_Base {
float proj[16];
float proj_through[16];
float view[12];
@@ -29,24 +28,22 @@ struct UB_VS_FS_Base {
float uvScaleOffset[4];
float depthRange[4];
// Rotation is used only for software transform.
- float fogCoef[2]; float stencil; float rotation;
float matAmbient[4];
float cullRangeMin[4];
float cullRangeMax[4];
uint32_t spline_counts; uint32_t depal_mask_shift_off_fmt; // 4 params packed into one.
uint32_t colorWriteMask; float mipBias;
// Fragment data
- float fogColor[4];
- float texEnvColor[4];
+ float fogColor[4]; // .w is unused
+ float texEnvColor[3]; uint32_t colorTestMask;
int alphaColorRef[4];
- int colorTestMask[4];
- float blendFixA[4];
- float blendFixB[4];
+ float blendFixA[3]; float stencil;
+ float blendFixB[3]; float rotation;
float texClamp[4];
- float texClampOffset[4];
+ float texClampOffset[2]; float fogCoef[2];
};
-static const char *ub_baseStr =
+static const char * const ub_baseStr =
R"( mat4 u_proj;
mat4 u_proj_through;
mat3x4 u_view;
@@ -54,9 +51,6 @@ R"( mat4 u_proj;
mat3x4 u_texmtx;
vec4 u_uvscaleoffset;
vec4 u_depthRange;
- vec2 u_fogcoef;
- float u_stencilReplaceValue;
- float u_rotation;
vec4 u_matambientalpha;
vec4 u_cullRangeMin;
vec4 u_cullRangeMax;
@@ -66,21 +60,23 @@ R"( mat4 u_proj;
float u_mipBias;
vec3 u_fogcolor;
vec3 u_texenv;
+ uint u_alphacolormask;
ivec4 u_alphacolorref;
- ivec4 u_alphacolormask;
- vec3 u_blendFixA;
- vec3 u_blendFixB;
+ vec3 u_blendFixA; float u_stencilReplaceValue;
+ vec3 u_blendFixB; float u_rotation;
vec4 u_texclamp;
vec2 u_texclampoff;
+ vec2 u_fogcoef;
)";
// 512 bytes. Would like to shrink more. Some colors only have 8-bit precision and we expand
// them to float unnecessarily, could just as well expand in the shader.
-struct UB_VS_Lights {
+struct alignas(16) UB_VS_Lights {
float ambientColor[4];
float materialDiffuse[4];
float materialSpecular[4];
- float materialEmissive[4];
+ float materialEmissive[3];
+ uint32_t lightControl;
float lpos[4][4];
float ldir[4][4];
float latt[4][4];
@@ -90,11 +86,12 @@ struct UB_VS_Lights {
float lightSpecular[4][4];
};
-static const char *ub_vs_lightsStr =
+static const char * const ub_vs_lightsStr =
R"( vec4 u_ambient;
vec3 u_matdiffuse;
vec4 u_matspecular;
vec3 u_matemissive;
+ uint u_lightControl; // light ubershader
vec3 u_lightpos0;
vec3 u_lightpos1;
vec3 u_lightpos2;
@@ -127,11 +124,11 @@ R"( vec4 u_ambient;
// With some cleverness, we could get away with uploading just half this when only the four or five first
// bones are being used. This is 384b.
-struct UB_VS_Bones {
+struct alignas(16) UB_VS_Bones {
float bones[8][12];
};
-static const char *ub_vs_bonesStr =
+static const char * const ub_vs_bonesStr =
R"( mat3x4 u_bone0; mat3x4 u_bone1; mat3x4 u_bone2; mat3x4 u_bone3; mat3x4 u_bone4; mat3x4 u_bone5; mat3x4 u_bone6; mat3x4 u_bone7; mat3x4 u_bone8;
)";
@@ -141,3 +138,4 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView
void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms);
void BoneUpdateUniforms(UB_VS_Bones *ub, uint64_t dirtyUniforms);
+uint32_t PackLightControlBits();
diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp
index 98b5adc079..5e3c0c5850 100644
--- a/GPU/Common/SoftwareTransformCommon.cpp
+++ b/GPU/Common/SoftwareTransformCommon.cpp
@@ -168,6 +168,29 @@ void SoftwareTransform::SetProjMatrix(float mtx[14], bool invertedX, bool invert
projMatrix_.translateAndScale(trans, scale);
}
+static void ReadWeightedNormal(Vec3f &source, VertexReader &reader, u32 vertType, bool skinningEnabled) {
+ if (reader.hasNormal())
+ reader.ReadNrm(source.AsArray());
+ if (skinningEnabled) {
+ float weights[8];
+ reader.ReadWeights(weights);
+
+ // Have to recalculate this, unfortunately. Please use software skinning...
+ Vec3f nsum(0, 0, 0);
+ for (int i = 0; i < vertTypeGetNumBoneWeights(vertType); i++) {
+ if (weights[i] != 0.0f) {
+ Vec3f norm;
+ Norm3ByMatrix43(norm.AsArray(), source.AsArray(), gstate.boneMatrix + i * 12);
+ nsum += norm * weights[i];
+ }
+ }
+
+ source = nsum;
+ }
+ if (gstate.areNormalsReversed())
+ source = -source;
+}
+
void SoftwareTransform::Decode(int prim, u32 vertType, const DecVtxFormat &decVtxFormat, int maxIndex, SoftwareTransformResult *result) {
u8 *decoded = params_.decoded;
TransformedVertex *transformed = params_.transformed;
@@ -284,7 +307,7 @@ void SoftwareTransform::Decode(int prim, u32 vertType, const DecVtxFormat &decVt
}
} else {
float weights[8];
- // TODO: For flat, are weights from the provoking used for color/normal?
+ // For flat, we need the vertex weights.
reader.Goto(index);
reader.ReadWeights(weights);
@@ -358,10 +381,8 @@ void SoftwareTransform::Decode(int prim, u32 vertType, const DecVtxFormat &decVt
case GE_TEXMAP_TEXTURE_MATRIX:
{
- // TODO: What's the correct behavior with flat shading? Provoked normal or real normal?
-
// Projection mapping
- Vec3f source;
+ Vec3f source(0.0f, 0.0f, 1.0f);
switch (gstate.getUVProjMode()) {
case GE_PROJMAP_POSITION: // Use model space XYZ as source
source = pos;
@@ -372,14 +393,28 @@ void SoftwareTransform::Decode(int prim, u32 vertType, const DecVtxFormat &decVt
break;
case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized normal as source
- source = normal.NormalizedOr001(cpu_info.bSSE4_1);
+ // Flat uses the vertex normal, not provoking.
+ if (provokeIndOffset == 0) {
+ source = normal.Normalized(cpu_info.bSSE4_1);
+ } else {
+ reader.Goto(index);
+ ReadWeightedNormal(source, reader, vertType, skinningEnabled);
+ source.Normalize();
+ }
if (!reader.hasNormal()) {
ERROR_LOG_REPORT(G3D, "Normal projection mapping without normal?");
}
break;
case GE_PROJMAP_NORMAL: // Use non-normalized normal as source!
- source = normal;
+ // Flat uses the vertex normal, not provoking.
+ if (provokeIndOffset == 0) {
+ source = normal;
+ } else {
+ // Need to read the normal for this vertex and weight it again..
+ reader.Goto(index);
+ ReadWeightedNormal(source, reader, vertType, skinningEnabled);
+ }
if (!reader.hasNormal()) {
ERROR_LOG_REPORT(G3D, "Normal projection mapping without normal?");
}
@@ -790,13 +825,13 @@ void SoftwareTransform::ExpandLines(int vertexCount, int &maxIndex, u16 *&inds,
float yoff = addWidth.y * dy;
// bottom right
- trans[0].CopyFromWithOffset(transVtx2, xoff, yoff);
+ trans[0].CopyFromWithOffset(transVtx2, xoff * transVtx2.pos_w, yoff * transVtx2.pos_w);
// top right
- trans[1].CopyFromWithOffset(transVtx1, xoff, yoff);
+ trans[1].CopyFromWithOffset(transVtx1, xoff * transVtx1.pos_w, yoff * transVtx1.pos_w);
// top left
- trans[2].CopyFromWithOffset(transVtx1, -xoff, -yoff);
+ trans[2].CopyFromWithOffset(transVtx1, -xoff * transVtx1.pos_w, -yoff * transVtx1.pos_w);
// bottom left
- trans[3].CopyFromWithOffset(transVtx2, -xoff, -yoff);
+ trans[3].CopyFromWithOffset(transVtx2, -xoff * transVtx2.pos_w, -yoff * transVtx2.pos_w);
// Triangle: BR-TR-TL
indsOut[0] = i * 2 + 0;
@@ -835,17 +870,17 @@ void SoftwareTransform::ExpandLines(int vertexCount, int &maxIndex, u16 *&inds,
// bottom right
trans[0] = transVtxBL;
- trans[0].x += addWidth.x * dx;
- trans[0].y += addWidth.y * dy;
- trans[0].u += addWidth.x * du;
- trans[0].v += addWidth.y * dv;
+ trans[0].x += addWidth.x * dx * trans[0].pos_w;
+ trans[0].y += addWidth.y * dy * trans[0].pos_w;
+ trans[0].u += addWidth.x * du * trans[0].uv_w;
+ trans[0].v += addWidth.y * dv * trans[0].uv_w;
// top right
trans[1] = transVtxTL;
- trans[1].x += addWidth.x * dx;
- trans[1].y += addWidth.y * dy;
- trans[1].u += addWidth.x * du;
- trans[1].v += addWidth.y * dv;
+ trans[1].x += addWidth.x * dx * trans[1].pos_w;
+ trans[1].y += addWidth.y * dy * trans[1].pos_w;
+ trans[1].u += addWidth.x * du * trans[1].uv_w;
+ trans[1].v += addWidth.y * dv * trans[1].uv_w;
// top left
trans[2] = transVtxTL;
diff --git a/GPU/Common/SplineCommon.cpp b/GPU/Common/SplineCommon.cpp
index d3f864a4d4..b2358f401b 100644
--- a/GPU/Common/SplineCommon.cpp
+++ b/GPU/Common/SplineCommon.cpp
@@ -577,7 +577,8 @@ void DrawEngineCommon::SubmitCurve(const void *control_points, const void *indic
if (output.count)
DispatchSubmitPrim(output.vertices, output.indices, PatchPrimToPrim(surface.primType), output.count, vertTypeID, gstate.getCullMode(), &generatedBytesRead);
- DispatchFlush();
+ if (flushOnParams_)
+ DispatchFlush();
if (origVertType & GE_VTYPE_TC_MASK) {
gstate_c.uv = prevUVScale;
diff --git a/GPU/Common/StencilCommon.cpp b/GPU/Common/StencilCommon.cpp
index 81749ef678..d30327df93 100644
--- a/GPU/Common/StencilCommon.cpp
+++ b/GPU/Common/StencilCommon.cpp
@@ -171,6 +171,7 @@ bool FramebufferManagerCommon::PerformStencilUpload(u32 addr, int size, StencilU
break;
case GE_FORMAT_INVALID:
case GE_FORMAT_DEPTH16:
+ case GE_FORMAT_CLUT8:
// Inconceivable.
_assert_(false);
break;
@@ -185,13 +186,9 @@ bool FramebufferManagerCommon::PerformStencilUpload(u32 addr, int size, StencilU
// Otherwise, we can skip alpha in many cases, in which case we don't even use a shader.
if (flags & StencilUpload::IGNORE_ALPHA) {
- shaderManager_->DirtyLastShader();
-
if (dstBuffer->fbo) {
draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }, "PerformStencilUpload_Clear");
}
-
- gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_DEPTHSTENCIL_STATE);
return true;
}
}
@@ -202,11 +199,14 @@ bool FramebufferManagerCommon::PerformStencilUpload(u32 addr, int size, StencilU
if (!stencilUploadPipeline_) {
const ShaderLanguageDesc &shaderLanguageDesc = draw_->GetShaderLanguageDesc();
- char *fsCode = new char[4000];
- char *vsCode = new char[4000];
+ char *fsCode = new char[8192];
+ char *vsCode = new char[8192];
GenerateStencilFs(fsCode, shaderLanguageDesc, draw_->GetBugs());
GenerateStencilVs(vsCode, shaderLanguageDesc);
+ _assert_msg_(strlen(fsCode) < 8192, "StenFS length error: %d", (int)strlen(fsCode));
+ _assert_msg_(strlen(vsCode) < 8192, "StenVS length error: %d", (int)strlen(vsCode));
+
ShaderModule *stencilUploadFs = draw_->CreateShaderModule(ShaderStage::Fragment, shaderLanguageDesc.shaderLanguage, (const uint8_t *)fsCode, strlen(fsCode), "stencil_fs");
ShaderModule *stencilUploadVs = draw_->CreateShaderModule(ShaderStage::Vertex, shaderLanguageDesc.shaderLanguage, (const uint8_t *)vsCode, strlen(vsCode), "stencil_vs");
@@ -332,6 +332,6 @@ bool FramebufferManagerCommon::PerformStencilUpload(u32 addr, int size, StencilU
tex->Release();
draw_->InvalidateCachedState();
- gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE);
+ gstate_c.Dirty(DIRTY_ALL_RENDER_STATE);
return true;
}
diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp
index 77b47e8b9e..cf012d4010 100644
--- a/GPU/Common/TextureCacheCommon.cpp
+++ b/GPU/Common/TextureCacheCommon.cpp
@@ -37,6 +37,7 @@
#include "GPU/Common/ShaderId.h"
#include "GPU/Common/GPUStateUtils.h"
#include "GPU/Debugger/Debugger.h"
+#include "GPU/Debugger/Record.h"
#include "GPU/GPUCommon.h"
#include "GPU/GPUInterface.h"
#include "GPU/GPUState.h"
@@ -151,7 +152,7 @@ static int TexLog2(float delta) {
}
SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, const TexCacheEntry *entry) {
- SamplerCacheKey key;
+ SamplerCacheKey key{};
int minFilt = gstate.texfilter & 0x7;
key.minFilt = minFilt & 1;
@@ -292,11 +293,18 @@ SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, const TexCac
SamplerCacheKey TextureCacheCommon::GetFramebufferSamplingParams(u16 bufferWidth, u16 bufferHeight) {
SamplerCacheKey key = GetSamplingParams(0, nullptr);
+ // In case auto max quality was on, restore min filt. Another fix for water in Outrun.
+ if (g_Config.iTexFiltering == TEX_FILTER_AUTO_MAX_QUALITY) {
+ int minFilt = gstate.texfilter & 0x7;
+ key.minFilt = minFilt & 1;
+ }
+
// Kill any mipmapping settings.
key.mipEnable = false;
key.mipFilt = false;
key.aniso = 0.0;
key.maxLevel = 0.0f;
+ key.lodBias = 0.0f;
// Often the framebuffer will not match the texture size. We'll wrap/clamp in the shader in that case.
int w = gstate.getTextureWidth(0);
@@ -377,13 +385,22 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
}
bool hasClut = gstate.isTextureFormatIndexed();
+ bool hasClutGPU = false;
u32 cluthash;
if (hasClut) {
- if (clutLastFormat_ != gstate.clutformat) {
- // We update here because the clut format can be specified after the load.
- UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple());
+ if (clutRenderAddress_ != 0xFFFFFFFF) {
+ gstate_c.curTextureXOffset = 0.0f;
+ gstate_c.curTextureYOffset = 0.0f;
+ hasClutGPU = true;
+ cluthash = 0; // Or should we use some other marker value?
+ } else {
+ if (clutLastFormat_ != gstate.clutformat) {
+ // We update here because the clut format can be specified after the load.
+ // TODO: Unify this as far as possible (I think only GLES backend really needs its own implementation due to different component order).
+ UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple());
+ }
+ cluthash = clutHash_ ^ gstate.clutformat;
}
- cluthash = clutHash_ ^ gstate.clutformat;
} else {
cluthash = 0;
}
@@ -401,10 +418,13 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
// Should probably revisit how this works..
gstate_c.SetNeedShaderTexclamp(false);
gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE;
- if (gstate_c.bgraTexture != isBgraBackend_) {
+
+ bool isBgraTexture = isBgraBackend_ && !hasClutGPU;
+
+ if (gstate_c.bgraTexture != isBgraTexture) {
gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE);
}
- gstate_c.bgraTexture = isBgraBackend_;
+ gstate_c.bgraTexture = isBgraTexture;
if (entryIter != cache_.end()) {
entry = entryIter->second.get();
@@ -412,6 +432,13 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
bool match = entry->Matches(dim, texFormat, maxLevel);
const char *reason = "different params";
+ // Check for dynamic CLUT status
+ if (((entry->status & TexCacheEntry::STATUS_CLUT_GPU) != 0) != hasClutGPU) {
+ // Need to recreate, suddenly a CLUT GPU texture was used without it, or vice versa.
+ // I think this can only happen on a clut hash collision with the marker value, so highly unlikely.
+ match = false;
+ }
+
// Check for FBO changes.
if (entry->status & TexCacheEntry::STATUS_FRAMEBUFFER_OVERLAP) {
// Fall through to the end where we'll delete the entry if there's a framebuffer.
@@ -542,6 +569,10 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
nextTexture_ = nullptr;
nextNeedsRebuild_ = false;
+
+ if (hasClutGPU) {
+ WARN_LOG_ONCE(clut_fb, G3D, "Render-to-CLUT combined with framebuffer texture at %08x - Not yet supported", texaddr);
+ }
SetTextureFramebuffer(bestCandidate); // sets curTexture3D
return nullptr;
}
@@ -553,10 +584,6 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
entry = new TexCacheEntry{};
cache_[cachekey].reset(entry);
- if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) {
- WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
- }
-
if (PPGeIsFontTextureAddress(texaddr)) {
// It's the builtin font texture.
entry->status = TexCacheEntry::STATUS_RELIABLE;
@@ -566,6 +593,11 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
entry->status = TexCacheEntry::STATUS_UNRELIABLE;
}
+ if (hasClutGPU) {
+ WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with dynamic CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat());
+ entry->status |= TexCacheEntry::STATUS_CLUT_GPU;
+ }
+
if (hasClut && clutRenderAddress_ == 0xFFFFFFFF) {
const u64 cachekeyMin = (u64)(texaddr & 0x3FFFFFFF) << 32;
const u64 cachekeyMax = cachekeyMin + (1ULL << 32);
@@ -656,7 +688,7 @@ bool TextureCacheCommon::GetBestFramebufferCandidate(const TextureDefinition &en
// Should avoid problems when pingponging two nearby buffers, like in Wipeout Pure in #15927.
if (candidate.channel == RASTER_COLOR &&
(candidate.match.yOffset != 0 || candidate.match.xOffset != 0) &&
- (candidate.fb->fb_address & 0x1FFFFF) == (gstate.getFrameBufAddress() & 0x1FFFFF)) {
+ candidate.fb->fb_address == (gstate.getFrameBufRawAddress() | 0x04000000)) {
relevancy -= 2;
}
@@ -813,10 +845,8 @@ void TextureCacheCommon::HandleTextureChange(TexCacheEntry *const entry, const c
}
void TextureCacheCommon::NotifyFramebuffer(VirtualFramebuffer *framebuffer, FramebufferNotification msg) {
- const u32 mirrorMask = 0x00600000;
const u32 fb_addr = framebuffer->fb_address;
-
- const u32 z_addr = framebuffer->z_address & ~mirrorMask; // Probably unnecessary.
+ const u32 z_addr = framebuffer->z_address;
const u32 fb_bpp = BufferFormatBytesPerPixel(framebuffer->fb_format);
const u32 z_bpp = 2; // No other format exists.
@@ -835,8 +865,6 @@ void TextureCacheCommon::NotifyFramebuffer(VirtualFramebuffer *framebuffer, Fram
// Try to match the new framebuffer to existing textures.
// Backwards from the "usual" texturing case so can't share a utility function.
- std::vector candidates;
-
u64 cacheKey = (u64)fb_addr << 32;
// If it has a clut, those are the low 32 bits, so it'll be inside this range.
// Also, if it's a subsample of the buffer, it'll also be within the FBO.
@@ -899,7 +927,7 @@ bool TextureCacheCommon::MatchFramebuffer(
uint32_t fb_stride_in_bytes = fb_stride * BufferFormatBytesPerPixel(fb_format);
uint32_t tex_stride_in_bytes = entry.bufw * textureBitsPerPixel[entry.format] / 8; // Note, we're looking up bits here so need to divide by 8.
- u32 addr = fb_address & 0x3FFFFFFF;
+ u32 addr = fb_address;
u32 texaddr = entry.addr + texaddrOffset;
bool texInVRAM = Memory::IsVRAMAddress(texaddr);
@@ -911,10 +939,10 @@ bool TextureCacheCommon::MatchFramebuffer(
}
if (texInVRAM) {
- const u32 mirrorMask = 0x00600000;
+ const u32 mirrorMask = 0x041FFFFF;
- addr &= ~mirrorMask;
- texaddr &= ~mirrorMask;
+ addr &= mirrorMask;
+ texaddr &= mirrorMask;
}
const bool noOffset = texaddr == addr;
@@ -947,12 +975,13 @@ bool TextureCacheCommon::MatchFramebuffer(
return false;
}
- // Check works for D16 too (???)
+ // Check works for D16 too.
const bool matchingClutFormat =
(fb_format == GE_FORMAT_DEPTH16 && entry.format == GE_TFMT_CLUT16) ||
(fb_format == GE_FORMAT_DEPTH16 && entry.format == GE_TFMT_5650) ||
(fb_format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT32) ||
- (fb_format != GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT16);
+ (fb_format != GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT16) ||
+ (fb_format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT8);
const int texBitsPerPixel = std::max(1U, (u32)textureBitsPerPixel[entry.format]);
const int byteOffset = texaddr - addr;
@@ -989,8 +1018,13 @@ bool TextureCacheCommon::MatchFramebuffer(
return false;
}
- if (fb_stride_in_bytes != tex_stride_in_bytes) {
+ // Note the check for texHeight - we really don't care about a stride mismatch if texHeight == 1.
+ if (fb_stride_in_bytes != tex_stride_in_bytes && texHeight > 1) {
// Probably irrelevant. Although, as we shall see soon, there are exceptions.
+ // Burnout Dominator lens flare trick special case.
+ if (fb_format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT8 && texWidth == 4 && texHeight == 1) {
+ return true;
+ }
return false;
}
@@ -1017,8 +1051,8 @@ bool TextureCacheCommon::MatchFramebuffer(
return true;
}
} else {
- WARN_LOG_ONCE(diffFormat2, G3D, "Ignoring possible texturing from framebuffer with incompatible format %s != %s at %08x",
- GeTextureFormatToString(entry.format), GeBufferFormatToString(fb_format), fb_address);
+ WARN_LOG_ONCE(diffFormat2, G3D, "Ignoring possible texturing from framebuffer with incompatible format %s != %s at %08x (+%dx%d)",
+ GeTextureFormatToString(entry.format), GeBufferFormatToString(fb_format), fb_address, matchInfo->xOffset, matchInfo->yOffset);
return false;
}
}
@@ -1167,47 +1201,94 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) {
if (Memory::IsValidAddress(clutAddr)) {
if (Memory::IsVRAMAddress(clutAddr)) {
- // Clear the uncached bit, etc. to match framebuffers.
- const u32 clutFramebufAddr = clutAddr & 0x3FFFFFFF;
- const u32 clutFramebufEnd = clutFramebufAddr + loadBytes;
+ // Clear the uncached and mirror bits, etc. to match framebuffers.
+ const u32 clutLoadAddr = clutAddr & 0x041FFFFF;
+ const u32 clutLoadEnd = clutLoadAddr + loadBytes;
static const u32 MAX_CLUT_OFFSET = 4096;
clutRenderOffset_ = MAX_CLUT_OFFSET;
const std::vector &framebuffers = framebufferManager_->Framebuffers();
- for (VirtualFramebuffer *framebuffer : framebuffers) {
- const u32 fb_address = framebuffer->fb_address & 0x3FFFFFFF;
- const u32 bpp = BufferFormatBytesPerPixel(framebuffer->fb_format);
- u32 offset = clutFramebufAddr - fb_address;
- // Is this inside the framebuffer at all?
- bool matchRange = fb_address + framebuffer->fb_stride * bpp > clutFramebufAddr && fb_address < clutFramebufEnd;
- // And is it inside the rendered area? Sometimes games pack data outside.
- bool matchRegion = ((offset / bpp) % framebuffer->fb_stride) < framebuffer->width;
- if (matchRange && matchRegion && offset < clutRenderOffset_) {
- WARN_LOG_N_TIMES(clutfb, 5, G3D, "Detected LoadCLUT(%d bytes) from framebuffer %08x (%s), byte offset %d", loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), offset);
- framebuffer->last_frame_clut = gpuStats.numFlips;
- framebuffer->usageFlags |= FB_USAGE_CLUT;
- clutRenderAddress_ = framebuffer->fb_address;
- clutRenderOffset_ = offset;
- if (offset == 0) {
- break;
+ u32 bestClutAddress = 0xFFFFFFFF;
+
+ VirtualFramebuffer *chosenFramebuffer = nullptr;
+ for (VirtualFramebuffer *framebuffer : framebuffers) {
+ const u32 fb_address = framebuffer->fb_address;
+ const u32 fb_bpp = BufferFormatBytesPerPixel(framebuffer->fb_format);
+ int offset = clutLoadAddr - fb_address;
+
+ // Is this inside the framebuffer at all? Note that we only check the first line here, this should
+ // be changed.
+ bool matchRange = offset >= 0 && offset < (int)(framebuffer->fb_stride * fb_bpp);
+ if (matchRange) {
+ // And is it inside the rendered area? Sometimes games pack data in the margin between width and stride.
+ // If the framebuffer width was detected as 512, we're gonna assume it's really 480.
+ int fbMatchWidth = framebuffer->width;
+ if (fbMatchWidth == 512) {
+ fbMatchWidth = 480;
+ }
+ bool inMargin = ((offset / fb_bpp) % framebuffer->fb_stride) == fbMatchWidth;
+
+ // The offset check here means, in the context of the loop, that we'll pick
+ // the framebuffer with the smallest offset. This is yet another framebuffer matching
+ // loop with its own rules, eventually we'll probably want to do something
+ // more systematic.
+ if (matchRange && !inMargin && offset < (int)clutRenderOffset_) {
+ WARN_LOG_N_TIMES(clutfb, 5, G3D, "Detected LoadCLUT(%d bytes) from framebuffer %08x (%s), byte offset %d", loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), offset);
+ framebuffer->last_frame_clut = gpuStats.numFlips;
+ framebuffer->usageFlags |= FB_USAGE_CLUT;
+ bestClutAddress = framebuffer->fb_address;
+ clutRenderOffset_ = (u32)offset;
+ chosenFramebuffer = framebuffer;
+ if (offset == 0) {
+ // Not gonna find a better match according to the smallest-offset rule, so we'll go with this one.
+ break;
+ }
}
}
}
+ // To turn off dynamic CLUT (for demonstration or testing purposes), add "false &&" to this check.
+ if (chosenFramebuffer && chosenFramebuffer->fbo) {
+ clutRenderAddress_ = bestClutAddress;
+
+ if (!dynamicClutTemp_) {
+ Draw::FramebufferDesc desc{};
+ desc.width = 512;
+ desc.height = 1;
+ desc.depth = 1;
+ desc.z_stencil = false;
+ desc.numColorAttachments = 1;
+ desc.tag = "dynamic_clut";
+ dynamicClutFbo_ = draw_->CreateFramebuffer(desc);
+ desc.tag = "dynamic_clut_temp";
+ dynamicClutTemp_ = draw_->CreateFramebuffer(desc);
+ }
+
+ // Copy the pixels to our temp clut, scaling down if needed and wrapping.
+ // TODO: Take the clutRenderOffset_ into account here.
+ framebufferManager_->BlitUsingRaster(
+ chosenFramebuffer->fbo, 0.0f, 0.0f, 512.0f * chosenFramebuffer->renderScaleFactor, 1.0f,
+ dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f,
+ false, chosenFramebuffer->renderScaleFactor, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR_RECT2LIN), "copy_clut_to_temp");
+ clutRenderFormat_ = chosenFramebuffer->fb_format;
+ }
NotifyMemInfo(MemBlockFlags::ALLOC, clutAddr, loadBytes, "CLUT");
}
// It's possible for a game to load CLUT outside valid memory without crashing, should result in zeroes.
u32 bytes = Memory::ValidSize(clutAddr, loadBytes);
- if (clutRenderAddress_ != 0xFFFFFFFF && PSP_CoreParameter().compat.flags().AllowDownloadCLUT) {
+ bool performDownload = PSP_CoreParameter().compat.flags().AllowDownloadCLUT;
+ if (GPURecord::IsActive())
+ performDownload = true;
+ if (clutRenderAddress_ != 0xFFFFFFFF && performDownload) {
framebufferManager_->DownloadFramebufferForClut(clutRenderAddress_, clutRenderOffset_ + bytes);
Memory::MemcpyUnchecked(clutBufRaw_, clutAddr, bytes);
if (bytes < loadBytes) {
memset((u8 *)clutBufRaw_ + bytes, 0x00, loadBytes - bytes);
}
} else {
- // Here we could check for clutRenderAddres_ != 0xFFFFFFFF and zero the CLUT or something,
+ // Here we could check for clutRenderAddress_ != 0xFFFFFFFF and zero the CLUT or something,
// but choosing not to for now. Though the results of loading the CLUT from RAM here is
// almost certainly going to be bogus.
#ifdef _M_SSE
@@ -1471,12 +1552,28 @@ inline u32 TfmtRawToFullAlpha(GETextureFormat fmt) {
}
}
+// Used for converting CLUT4 to CLUT8.
+// Could SIMD or whatever, though will hardly be a bottleneck.
+static void Expand4To8Bits(u8 *dest, const u8 *src, int srcWidth) {
+ for (int i = 0; i < (srcWidth + 1) / 2; i++) {
+ u8 lower = src[i] & 0xF;
+ u8 upper = src[i] >> 4;
+ dest[i * 2] = lower;
+ dest[i * 2 + 1] = upper;
+ }
+}
+
CheckAlphaResult TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureFormat format, GEPaletteFormat clutformat, uint32_t texaddr, int level, int bufw, TexDecodeFlags flags) {
u32 alphaSum = 0xFFFFFFFF;
u32 fullAlphaMask = 0x0;
bool expandTo32bit = (flags & TexDecodeFlags::EXPAND32) != 0;
bool reverseColors = (flags & TexDecodeFlags::REVERSE_COLORS) != 0;
+ bool toClut8 = (flags & TexDecodeFlags::TO_CLUT8) != 0;
+
+ if (toClut8 && format != GE_TFMT_CLUT8 && format != GE_TFMT_CLUT4) {
+ _dbg_assert_(false);
+ }
bool swizzled = gstate.isTextureSwizzled();
if ((texaddr & 0x00600000) != 0 && Memory::IsVRAMAddress(texaddr)) {
@@ -1511,6 +1608,15 @@ CheckAlphaResult TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, G
texptr = (u8 *)tmpTexBuf32_.data();
}
+ if (toClut8) {
+ // We just need to expand from 4 to 8 bits.
+ for (int y = 0; y < h; ++y) {
+ Expand4To8Bits((u8 *)out + outPitch * y, texptr + (bufw * y) / 2, w);
+ }
+ // We can't know anything about alpha.
+ return CHECKALPHA_ANY;
+ }
+
switch (clutformat) {
case GE_CMODE_16BIT_BGR5650:
case GE_CMODE_16BIT_ABGR5551:
@@ -1573,6 +1679,19 @@ CheckAlphaResult TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, G
break;
case GE_TFMT_CLUT8:
+ if (toClut8) {
+ if (gstate.isTextureSwizzled()) {
+ tmpTexBuf32_.resize(bufw * ((h + 7) & ~7));
+ UnswizzleFromMem(tmpTexBuf32_.data(), bufw, texptr, bufw, h, 1);
+ texptr = (u8 *)tmpTexBuf32_.data();
+ }
+ // After deswizzling, we are in the correct format and can just copy.
+ for (int y = 0; y < h; ++y) {
+ memcpy((u8 *)out + outPitch * y, texptr + (bufw * y), w);
+ }
+ // We can't know anything about alpha.
+ return CHECKALPHA_ANY;
+ }
return ReadIndexedTex(out, outPitch, level, texptr, 1, bufw, reverseColors, expandTo32bit);
case GE_TFMT_CLUT16:
@@ -1858,10 +1977,18 @@ void TextureCacheCommon::ApplyTexture() {
InvalidateLastTexture();
}
- entry->lastFrame = gpuStats.numFlips;
- BindTexture(entry);
- gstate_c.SetTextureFullAlpha(entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL);
- gstate_c.SetTextureIs3D((entry->status & TexCacheEntry::STATUS_3D) != 0);
+ if (entry->status & TexCacheEntry::STATUS_CLUT_GPU) {
+ // Special process.
+ ApplyTextureDepal(entry);
+ entry->lastFrame = gpuStats.numFlips;
+ gstate_c.SetTextureFullAlpha(false);
+ gstate_c.SetTextureIs3D(false);
+ } else {
+ entry->lastFrame = gpuStats.numFlips;
+ BindTexture(entry);
+ gstate_c.SetTextureFullAlpha(entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL);
+ gstate_c.SetTextureIs3D((entry->status & TexCacheEntry::STATUS_3D) != 0);
+ }
}
static bool CanDepalettize(GETextureFormat texFormat, GEBufferFormat bufferFormat) {
@@ -1876,10 +2003,13 @@ static bool CanDepalettize(GETextureFormat texFormat, GEBufferFormat bufferForma
}
break;
case GE_FORMAT_8888:
- if (texFormat == GE_TFMT_CLUT32) {
+ if (texFormat == GE_TFMT_CLUT32 || texFormat == GE_TFMT_CLUT8) { // clut8 takes a special depal mode.
return true;
}
break;
+ case GE_FORMAT_CLUT8:
+ // Shouldn't happen here.
+ return false;
}
WARN_LOG(G3D, "Invalid CLUT/framebuffer combination: %s vs %s", GeTextureFormatToString(texFormat), GeBufferFormatToString(bufferFormat));
return false;
@@ -1918,7 +2048,6 @@ static bool CanUseSmoothDepal(const GPUgstate &gstate, GEBufferFormat framebuffe
return false;
}
-
void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, RasterChannel channel) {
Draw2DPipeline *textureShader = nullptr;
uint32_t clutMode = gstate.clutformat & 0xFFFFFF;
@@ -1964,9 +2093,16 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer
samplerKey.mipEnable = false;
ApplySamplingParams(samplerKey);
- // Since we started/ended render passes, might need these.
+ ShaderDepalMode mode = ShaderDepalMode::NORMAL;
+ if (texFormat == GE_TFMT_CLUT8 && framebuffer->fb_format == GE_FORMAT_8888) {
+ mode = ShaderDepalMode::CLUT8_8888;
+ smoothedDepal = false; // just in case
+ } else if (smoothedDepal) {
+ mode = ShaderDepalMode::SMOOTHED;
+ }
+
gstate_c.Dirty(DIRTY_DEPAL);
- gstate_c.SetUseShaderDepal(smoothedDepal ? ShaderDepalMode::SMOOTHED : ShaderDepalMode::NORMAL);
+ gstate_c.SetUseShaderDepal(mode);
gstate_c.depalFramebufferFormat = framebuffer->fb_format;
const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16);
@@ -2062,8 +2198,100 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer
SamplerCacheKey samplerKey = GetFramebufferSamplingParams(framebuffer->bufferWidth, framebuffer->bufferHeight);
ApplySamplingParams(samplerKey);
- // Since we started/ended render passes, might need these.
- gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE);
+ // Since we've drawn using thin3d, might need these.
+ gstate_c.Dirty(DIRTY_ALL_RENDER_STATE);
+}
+
+// Applies depal to a normal (non-framebuffer) texture, pre-decoded to CLUT8 format.
+void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) {
+ uint32_t clutMode = gstate.clutformat & 0xFFFFFF;
+
+ switch (entry->format) {
+ case GE_TFMT_CLUT4:
+ case GE_TFMT_CLUT8:
+ break; // These are OK
+ default:
+ _dbg_assert_(false);
+ return;
+ }
+
+ const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat();
+ u32 depthUpperBits = 0;
+
+ // The CLUT texture is dynamic, it's the framebuffer pointed to by clutRenderAddress.
+ // Instead of texturing directly from that, we copy to a temporary CLUT texture.
+ GEBufferFormat expectedCLUTBufferFormat = (GEBufferFormat)clutFormat; // All entries from clutFormat correspond directly to buffer formats.
+
+ // OK, figure out what format we want our framebuffer in, so it can be reinterpreted if needed.
+ // If no reinterpretation is needed, we'll automatically just get a copy shader.
+ float scaleFactorX = 1.0f;
+ Draw2DPipeline *reinterpret = framebufferManager_->GetReinterpretPipeline(clutRenderFormat_, expectedCLUTBufferFormat, &scaleFactorX);
+ framebufferManager_->BlitUsingRaster(
+ dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, scaleFactorX * 512.0f, 1.0f, false, 1.0f, reinterpret, "reinterpret_clut");
+
+ Draw2DPipeline *textureShader = textureShaderCache_->GetDepalettizeShader(clutMode, GE_TFMT_CLUT8, GE_FORMAT_CLUT8, false, 0);
+ gstate_c.SetUseShaderDepal(ShaderDepalMode::OFF);
+
+ int texWidth = gstate.getTextureWidth(0);
+ int texHeight = gstate.getTextureHeight(0);
+
+ // If min is not < max, then we don't have values (wasn't set during decode.)
+ const KnownVertexBounds &bounds = gstate_c.vertBounds;
+ float u1 = 0.0f;
+ float v1 = 0.0f;
+ float u2 = texWidth;
+ float v2 = texHeight;
+ if (bounds.minV < bounds.maxV) {
+ u1 = (bounds.minU + gstate_c.curTextureXOffset) * texWidth;
+ v1 = (bounds.minV + gstate_c.curTextureYOffset) * texHeight;
+ u2 = (bounds.maxU + gstate_c.curTextureXOffset) * texWidth;
+ v2 = (bounds.maxV + gstate_c.curTextureYOffset) * texHeight;
+ // We need to reapply the texture next time since we cropped UV.
+ gstate_c.Dirty(DIRTY_TEXTURE_PARAMS);
+ }
+
+ Draw::Framebuffer *depalFBO = framebufferManager_->GetTempFBO(TempFBO::DEPAL, texWidth, texHeight);
+ draw_->BindTexture(0, nullptr);
+ draw_->BindTexture(1, nullptr);
+ draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Depal");
+ draw_->InvalidateFramebuffer(Draw::FB_INVALIDATION_STORE, Draw::FB_DEPTH_BIT | Draw::FB_STENCIL_BIT);
+ draw_->SetScissorRect(u1, v1, u2 - u1, v2 - v1);
+ Draw::Viewport vp{ 0.0f, 0.0f, (float)texWidth, (float)texHeight, 0.0f, 1.0f };
+ draw_->SetViewports(1, &vp);
+
+ draw_->BindNativeTexture(0, GetNativeTextureView(entry));
+ draw_->BindFramebufferAsTexture(dynamicClutFbo_, 1, Draw::FB_COLOR_BIT, 0);
+ Draw::SamplerState *nearest = textureShaderCache_->GetSampler(false);
+ Draw::SamplerState *clutSampler = textureShaderCache_->GetSampler(false);
+ draw_->BindSamplerStates(0, 1, &nearest);
+ draw_->BindSamplerStates(1, 1, &clutSampler);
+
+ draw2D_->Blit(textureShader, u1, v1, u2, v2, u1, v1, u2, v2, texWidth, texHeight, texWidth, texHeight, false, 1);
+
+ gpuStats.numDepal++;
+
+ gstate_c.curTextureWidth = texWidth;
+
+ draw_->BindTexture(0, nullptr);
+ framebufferManager_->RebindFramebuffer("ApplyTextureFramebuffer");
+
+ draw_->BindFramebufferAsTexture(depalFBO, 0, Draw::FB_COLOR_BIT, 0);
+ BoundFramebufferTexture();
+
+ const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16);
+ const u32 clutTotalColors = clutMaxBytes_ / bytesPerColor;
+
+ // We don't know about alpha at all.
+ gstate_c.SetTextureFullAlpha(false);
+
+ draw_->InvalidateCachedState();
+ shaderManager_->DirtyLastShader();
+
+ SamplerCacheKey samplerKey = GetFramebufferSamplingParams(texWidth, texHeight);
+ ApplySamplingParams(samplerKey);
+
+ // Since we've drawn using thin3d, might need these.
+ gstate_c.Dirty(DIRTY_ALL_RENDER_STATE);
}
void TextureCacheCommon::Clear(bool delete_them) {
@@ -2085,6 +2313,15 @@ void TextureCacheCommon::Clear(bool delete_them) {
secondCacheSizeEstimate_ = 0;
}
videos_.clear();
+
+ if (dynamicClutFbo_) {
+ dynamicClutFbo_->Release();
+ dynamicClutFbo_ = nullptr;
+ }
+ if (dynamicClutTemp_) {
+ dynamicClutTemp_->Release();
+ dynamicClutTemp_ = nullptr;
+ }
}
void TextureCacheCommon::DeleteTexture(TexCache::iterator it) {
@@ -2402,14 +2639,32 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt
}
}
- if (isPPGETexture) {
- plan.replaced = &replacer_.FindNone();
- plan.replaceValid = false;
+ bool canReplace = !isPPGETexture;
+ if (entry->status & TexCacheEntry::TexStatus::STATUS_CLUT_GPU) {
+ _dbg_assert_(entry->format == GE_TFMT_CLUT4 || entry->format == GE_TFMT_CLUT8);
+ plan.decodeToClut8 = true;
+ // We only support 1 mip level when doing CLUT on GPU for now.
+ // Supporting more would be possible, just not very interesting until we need it.
+ plan.levelsToCreate = 1;
+ plan.levelsToLoad = 1;
+ plan.maxPossibleLevels = 1;
+ plan.scaleFactor = 1;
+ plan.saveTexture = false; // Can't yet save these properly.
+ canReplace = false;
} else {
+ plan.decodeToClut8 = false;
+ }
+
+ if (canReplace) {
plan.replaced = &FindReplacement(entry, plan.w, plan.h, plan.depth);
plan.replaceValid = plan.replaced->Valid();
+ } else {
+ plan.replaced = &replacer_.FindNone();
+ plan.replaceValid = false;
}
+ // NOTE! Last chance to change scale factor here!
+
plan.saveTexture = false;
if (plan.replaceValid) {
// We're replacing, so we won't scale.
@@ -2420,7 +2675,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt
// But, we still need to create the texture at a larger size.
plan.replaced->GetSize(0, plan.createW, plan.createH);
} else {
- if (replacer_.Enabled() && !plan.replaceValid && plan.depth == 1) {
+ if (replacer_.Enabled() && !plan.replaceValid && plan.depth == 1 && canReplace) {
ReplacedTextureDecodeInfo replacedInfo;
// TODO: Do we handle the race where a replacement becomes valid AFTER this but before we save?
replacedInfo.cachekey = entry->CacheKey();
@@ -2445,7 +2700,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt
plan.levelsToLoad = 1;
}
- if (plan.isVideo || plan.depth != 1) {
+ if (plan.isVideo || plan.depth != 1 || plan.decodeToClut8) {
plan.maxPossibleLevels = 1;
} else {
plan.maxPossibleLevels = log2i(std::min(plan.createW, plan.createH)) + 1;
@@ -2492,6 +2747,9 @@ void TextureCacheCommon::LoadTextureLevel(TexCacheEntry &entry, uint8_t *data, i
if (!gstate_c.Supports(GPU_SUPPORTS_16BIT_FORMATS) || dstFmt == Draw::DataFormat::R8G8B8A8_UNORM) {
texDecFlags |= TexDecodeFlags::EXPAND32;
}
+ if (entry.status & TexCacheEntry::STATUS_CLUT_GPU) {
+ texDecFlags |= TexDecodeFlags::TO_CLUT8;
+ }
CheckAlphaResult alphaResult = DecodeTextureLevel((u8 *)pixelData, decPitch, tfmt, clutformat, texaddr, srcLevel, bufw, texDecFlags);
entry.SetAlphaStatus(alphaResult, srcLevel);
@@ -2540,7 +2798,7 @@ CheckAlphaResult TextureCacheCommon::CheckCLUTAlpha(const uint8_t *pixelData, GE
// Never has any alpha.
return CHECKALPHA_FULL;
default:
- return CheckAlpha32((const u32 *)pixelData, w, 0xFF000000); // note, the normal order here, unlike the 16-bit formats
+ return CheckAlpha32((const u32 *)pixelData, w, 0xFF000000);
}
}
diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h
index 78ad53df1f..9922e9070a 100644
--- a/GPU/Common/TextureCacheCommon.h
+++ b/GPU/Common/TextureCacheCommon.h
@@ -53,6 +53,7 @@ class ShaderManagerCommon;
enum class TexDecodeFlags {
EXPAND32 = 1,
REVERSE_COLORS = 2,
+ TO_CLUT8 = 4,
};
ENUM_CLASS_BITOPS(TexDecodeFlags);
@@ -108,6 +109,8 @@ struct TextureDefinition {
// NOTE: These only handle textures loaded directly from PSP memory contents.
// Framebuffer textures do not have entries, we bind the framebuffers directly.
+// At one point we might merge the concepts of framebuffers and textures, but that
+// moment is far away.
struct TexCacheEntry {
~TexCacheEntry() {
if (texturePtr || textureName || vkTex)
@@ -144,6 +147,8 @@ struct TexCacheEntry {
STATUS_FORCE_REBUILD = 0x2000,
STATUS_3D = 0x4000,
+
+ STATUS_CLUT_GPU = 0x8000,
};
// Status, but int so we can zero initialize.
@@ -281,6 +286,9 @@ struct BuildTexturePlan {
bool replaceValid;
bool saveTexture;
+ // TODO: Expand32 should probably also be decided in PrepareBuildTexture.
+ bool decodeToClut8;
+
void GetMipSize(int level, int *w, int *h) const {
if (replaceValid) {
replaced->GetSize(level, *w, *h);
@@ -343,6 +351,7 @@ public:
virtual bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) { return false; }
protected:
+ virtual void *GetNativeTextureView(const TexCacheEntry *entry) = 0;
bool PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEntry *entry);
virtual void BindTexture(TexCacheEntry *entry) = 0;
@@ -352,6 +361,7 @@ protected:
void Decimate(bool forcePressure = false);
void ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, RasterChannel channel);
+ void ApplyTextureDepal(TexCacheEntry *entry);
void HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete);
virtual void BuildTexture(TexCacheEntry *const entry) = 0;
@@ -476,10 +486,16 @@ protected:
u32 clutMaxBytes_ = 0;
u32 clutRenderAddress_ = 0xFFFFFFFF;
u32 clutRenderOffset_;
+ GEBufferFormat clutRenderFormat_;
+
// True if the clut is just alpha values in the same order (RGBA4444-bit only.)
bool clutAlphaLinear_ = false;
u16 clutAlphaLinearColor_;
+ // Facilities for GPU depal of static textures.
+ Draw::Framebuffer *dynamicClutTemp_ = nullptr;
+ Draw::Framebuffer *dynamicClutFbo_ = nullptr;
+
int standardScaleFactor_;
int shaderScaleFactor_ = 0;
diff --git a/GPU/Common/TextureShaderCommon.cpp b/GPU/Common/TextureShaderCommon.cpp
index f364016e72..2860389fc2 100644
--- a/GPU/Common/TextureShaderCommon.cpp
+++ b/GPU/Common/TextureShaderCommon.cpp
@@ -102,7 +102,7 @@ ClutTexture TextureShaderCache::GetClutTexture(GEPaletteFormat clutFormat, const
int lastA = 0;
int rampLength = 0;
- // Quick check for how many continouosly growing entries we have at the start.
+ // Quick check for how many continuously growing entries we have at the start.
// Bilinearly filtering CLUTs only really makes sense for this kind of ramp.
for (int i = 0; i < maxClutEntries; i++) {
rampLength = i;
@@ -209,7 +209,6 @@ Draw2DPipeline *TextureShaderCache::GetDepalettizeShader(uint32_t clutMode, GETe
config.smoothedDepal = smoothedDepal;
config.depthUpperBits = depthUpperBits;
- char *buffer = new char[4096];
Draw2DPipeline *ts = draw2D_->Create2DPipeline([=](ShaderWriter &writer) -> Draw2DPipelineInfo {
GenerateDepalFs(writer, config);
return Draw2DPipelineInfo{
@@ -219,7 +218,6 @@ Draw2DPipeline *TextureShaderCache::GetDepalettizeShader(uint32_t clutMode, GETe
samplers
};
});
- delete[] buffer;
depalCache_[id] = ts;
diff --git a/GPU/Common/VertexDecoderArm.cpp b/GPU/Common/VertexDecoderArm.cpp
index e3d5b11f6b..2b93b563f8 100644
--- a/GPU/Common/VertexDecoderArm.cpp
+++ b/GPU/Common/VertexDecoderArm.cpp
@@ -872,22 +872,14 @@ void VertexDecoderJitCache::Jit_NormalFloat() {
STMIA(scratchReg, false, 3, tempReg1, tempReg2, tempReg3);
}
-// Through expands into floats, always. Might want to look at changing this.
void VertexDecoderJitCache::Jit_PosS8Through() {
- DEBUG_LOG_REPORT_ONCE(vertexS8Through, G3D, "Using S8 positions in throughmode");
_dbg_assert_msg_(fpScratchReg + 1 == fpScratchReg2, "VertexDecoder fpScratchRegs must be in order.");
_dbg_assert_msg_(fpScratchReg2 + 1 == fpScratchReg3, "VertexDecoder fpScratchRegs must be in order.");
- // TODO: SIMD
- LDRSB(tempReg1, srcReg, dec_->posoff);
- LDRSB(tempReg2, srcReg, dec_->posoff + 1);
- LDRB(tempReg3, srcReg, dec_->posoff + 2);
- static const ARMReg tr[3] = { tempReg1, tempReg2, tempReg3 };
- static const ARMReg fr[3] = { fpScratchReg, fpScratchReg2, fpScratchReg3 };
+ // 8-bit positions in throughmode always decode to 0, depth included.
+ VEOR(neonScratchReg, neonScratchReg, neonScratchReg);
+ VEOR(neonScratchReg2, neonScratchReg, neonScratchReg);
ADD(scratchReg, dstReg, dec_->decFmt.posoff);
- VMOV(neonScratchReg, tempReg1, tempReg2);
- VMOV(neonScratchReg2, tempReg3, tempReg3);
- VCVT(F_32 | I_SIGNED, neonScratchRegQ, neonScratchRegQ);
VST1(F_32, neonScratchReg, scratchReg, 2, ALIGN_NONE);
}
diff --git a/GPU/Common/VertexDecoderArm64.cpp b/GPU/Common/VertexDecoderArm64.cpp
index 5ff1d605f3..0ad04dbe5c 100644
--- a/GPU/Common/VertexDecoderArm64.cpp
+++ b/GPU/Common/VertexDecoderArm64.cpp
@@ -668,15 +668,11 @@ void VertexDecoderJitCache::Jit_PosFloat() {
}
void VertexDecoderJitCache::Jit_PosS8Through() {
- LDRSB(INDEX_UNSIGNED, tempReg1, srcReg, dec_->posoff);
- LDRSB(INDEX_UNSIGNED, tempReg2, srcReg, dec_->posoff + 1);
- LDRB(INDEX_UNSIGNED, tempReg3, srcReg, dec_->posoff + 2);
- fp.SCVTF(fpScratchReg, tempReg1);
- fp.SCVTF(fpScratchReg2, tempReg2);
- fp.SCVTF(fpScratchReg3, tempReg3);
+ // 8-bit positions in throughmode always decode to 0, depth included.
+ fp.EOR(fpScratchReg, fpScratchReg, fpScratchReg);
STR(INDEX_UNSIGNED, fpScratchReg, dstReg, dec_->decFmt.posoff);
- STR(INDEX_UNSIGNED, fpScratchReg2, dstReg, dec_->decFmt.posoff + 4);
- STR(INDEX_UNSIGNED, fpScratchReg3, dstReg, dec_->decFmt.posoff + 8);
+ STR(INDEX_UNSIGNED, fpScratchReg, dstReg, dec_->decFmt.posoff + 4);
+ STR(INDEX_UNSIGNED, fpScratchReg, dstReg, dec_->decFmt.posoff + 8);
}
void VertexDecoderJitCache::Jit_PosS16Through() {
diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp
index a9b4a1039a..62eb20ef84 100644
--- a/GPU/Common/VertexDecoderCommon.cpp
+++ b/GPU/Common/VertexDecoderCommon.cpp
@@ -773,14 +773,20 @@ void VertexDecoder::Step_PosFloatSkin() const
Vec3ByMatrix43(pos, fn, skinMatrix);
}
-void VertexDecoder::Step_PosS8Through() const
-{
+void VertexDecoder::Step_PosInvalid() const {
+ // Invalid positions are just culled. Simulate by forcing invalid values.
float *v = (float *)(decoded_ + decFmt.posoff);
- const s8 *sv = (const s8 *)(ptr_ + posoff);
- const u8 *uv = (const u8 *)(ptr_ + posoff);
- v[0] = sv[0];
- v[1] = sv[1];
- v[2] = uv[2];
+ v[0] = std::numeric_limits::infinity();
+ v[1] = std::numeric_limits::infinity();
+ v[2] = std::numeric_limits::infinity();
+}
+
+void VertexDecoder::Step_PosS8Through() const {
+ // 8-bit positions in throughmode always decode to 0, depth included.
+ float *v = (float *)(decoded_ + decFmt.posoff);
+ v[0] = 0;
+ v[1] = 0;
+ v[2] = 0;
}
void VertexDecoder::Step_PosS16Through() const
@@ -1023,35 +1029,35 @@ static const StepFunction nrmstep_morphskin[4] = {
};
static const StepFunction posstep[4] = {
- &VertexDecoder::Step_PosS8,
+ &VertexDecoder::Step_PosInvalid,
&VertexDecoder::Step_PosS8,
&VertexDecoder::Step_PosS16,
&VertexDecoder::Step_PosFloat,
};
static const StepFunction posstep_skin[4] = {
- &VertexDecoder::Step_PosS8Skin,
+ &VertexDecoder::Step_PosInvalid,
&VertexDecoder::Step_PosS8Skin,
&VertexDecoder::Step_PosS16Skin,
&VertexDecoder::Step_PosFloatSkin,
};
static const StepFunction posstep_morph[4] = {
- &VertexDecoder::Step_PosS8Morph,
+ &VertexDecoder::Step_PosInvalid,
&VertexDecoder::Step_PosS8Morph,
&VertexDecoder::Step_PosS16Morph,
&VertexDecoder::Step_PosFloatMorph,
};
static const StepFunction posstep_morph_skin[4] = {
- &VertexDecoder::Step_PosS8MorphSkin,
+ &VertexDecoder::Step_PosInvalid,
&VertexDecoder::Step_PosS8MorphSkin,
&VertexDecoder::Step_PosS16MorphSkin,
&VertexDecoder::Step_PosFloatMorphSkin,
};
static const StepFunction posstep_through[4] = {
- &VertexDecoder::Step_PosS8Through,
+ &VertexDecoder::Step_PosInvalid,
&VertexDecoder::Step_PosS8Through,
&VertexDecoder::Step_PosS16Through,
&VertexDecoder::Step_PosFloatThrough,
@@ -1224,9 +1230,8 @@ void VertexDecoder::SetVertexType(u32 fmt, const VertexDecoderOptions &options,
bool reportNoPos = false;
if (!pos) {
reportNoPos = true;
- pos = 1;
}
- if (pos) { // there's always a position
+ if (pos >= 0) { // there's always a position
size = align(size, posalign[pos]);
posoff = size;
size += possize[pos];
diff --git a/GPU/Common/VertexDecoderCommon.h b/GPU/Common/VertexDecoderCommon.h
index 983f76f9b2..6a06093902 100644
--- a/GPU/Common/VertexDecoderCommon.h
+++ b/GPU/Common/VertexDecoderCommon.h
@@ -433,6 +433,7 @@ public:
void Step_PosS16MorphSkin() const;
void Step_PosFloatMorphSkin() const;
+ void Step_PosInvalid() const;
void Step_PosS8Through() const;
void Step_PosS16Through() const;
void Step_PosFloatThrough() const;
diff --git a/GPU/Common/VertexDecoderX86.cpp b/GPU/Common/VertexDecoderX86.cpp
index d684078989..3618071c76 100644
--- a/GPU/Common/VertexDecoderX86.cpp
+++ b/GPU/Common/VertexDecoderX86.cpp
@@ -1345,14 +1345,9 @@ void VertexDecoderJitCache::Jit_NormalFloatSkin() {
// Through expands into floats, always. Might want to look at changing this.
void VertexDecoderJitCache::Jit_PosS8Through() {
- DEBUG_LOG_REPORT_ONCE(vertexS8Through, G3D, "Using S8 positions in throughmode");
// SIMD doesn't really matter since this isn't useful on hardware.
+ XORPS(fpScratchReg, R(fpScratchReg));
for (int i = 0; i < 3; i++) {
- if (i == 2)
- MOVZX(32, 8, tempReg1, MDisp(srcReg, dec_->posoff + i));
- else
- MOVSX(32, 8, tempReg1, MDisp(srcReg, dec_->posoff + i));
- CVTSI2SS(fpScratchReg, R(tempReg1));
MOVSS(MDisp(dstReg, dec_->decFmt.posoff + i * 4), fpScratchReg);
}
}
@@ -1396,7 +1391,13 @@ void VertexDecoderJitCache::Jit_PosFloatThrough() {
CVTTPS2DQ(fpScratchReg, R(fpScratchReg));
// Use pack to saturate to 0,65535.
- PACKUSDW(fpScratchReg, R(fpScratchReg));
+ if (cpu_info.bSSE4_1) {
+ PACKUSDW(fpScratchReg, R(fpScratchReg));
+ } else {
+ PSLLD(fpScratchReg, 16);
+ PSRAD(fpScratchReg, 16);
+ PACKSSDW(fpScratchReg, R(fpScratchReg));
+ }
PUNPCKLWD(fpScratchReg, R(fpScratchReg2));
CVTDQ2PS(fpScratchReg, R(fpScratchReg));
diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp
index ccda9af86b..24f118603f 100644
--- a/GPU/Common/VertexShaderGenerator.cpp
+++ b/GPU/Common/VertexShaderGenerator.cpp
@@ -127,9 +127,6 @@ static const char * const boneWeightDecl[9] = {
"layout(location = 3) in vec4 w1;\nlayout(location = 4) in vec4 w2;\n",
};
-extern const char *vulkan_glsl_preamble_vs;
-extern const char *hlsl_preamble_vs;
-
bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguageDesc &compat, Draw::Bugs bugs, uint32_t *attrMask, uint64_t *uniformMask, std::string *errorString) {
*attrMask = 0;
*uniformMask = 0;
@@ -142,10 +139,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (gl_extensions.EXT_gpu_shader4) {
gl_exts.push_back("#extension GL_EXT_gpu_shader4 : enable");
}
- if (gl_extensions.EXT_clip_cull_distance && id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) {
+ bool useClamp = gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && !id.Bit(VS_BIT_IS_THROUGH);
+ if (gl_extensions.EXT_clip_cull_distance && (id.Bit(VS_BIT_VERTEX_RANGE_CULLING) || useClamp)) {
gl_exts.push_back("#extension GL_EXT_clip_cull_distance : enable");
}
- if (gl_extensions.APPLE_clip_distance && id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) {
+ if (gl_extensions.APPLE_clip_distance && (id.Bit(VS_BIT_VERTEX_RANGE_CULLING) || useClamp)) {
gl_exts.push_back("#extension GL_APPLE_clip_distance : enable");
}
if (gl_extensions.ARB_cull_distance && id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) {
@@ -176,7 +174,6 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
bool hasColor = id.Bit(VS_BIT_HAS_COLOR) || !useHWTransform;
bool hasNormal = id.Bit(VS_BIT_HAS_NORMAL) && useHWTransform;
bool hasTexcoord = id.Bit(VS_BIT_HAS_TEXCOORD) || !useHWTransform;
- bool enableFog = id.Bit(VS_BIT_ENABLE_FOG);
bool flipNormal = id.Bit(VS_BIT_NORM_REVERSE);
int ls0 = id.Bits(VS_BIT_LS0, 2);
int ls1 = id.Bits(VS_BIT_LS1, 2);
@@ -184,6 +181,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
bool enableLighting = id.Bit(VS_BIT_LIGHTING_ENABLE);
int matUpdate = id.Bits(VS_BIT_MATERIAL_UPDATE, 3);
+ bool lightUberShader = id.Bit(VS_BIT_LIGHT_UBERSHADER) && enableLighting; // checking lighting here for the shader test's benefit, in reality if ubershader is set, lighting is set.
+ if (lightUberShader && !compat.bitwiseOps) {
+ *errorString = "Light ubershader requires bitwise ops in shader language";
+ return false;
+ }
+
// Apparently we don't support bezier/spline together with bones.
bool doBezier = id.Bit(VS_BIT_BEZIER) && !enableBones && useHWTransform;
bool doSpline = id.Bit(VS_BIT_SPLINE) && !enableBones && useHWTransform;
@@ -227,6 +230,9 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
bool texCoordInVec3 = false;
bool vertexRangeCulling = id.Bit(VS_BIT_VERTEX_RANGE_CULLING) && !isModeThrough;
+ bool clipClampedDepth = !isModeThrough && gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE);
+ const char *clipClampedDepthSuffix = "[0]";
+ const char *vertexRangeClipSuffix = clipClampedDepth ? "[1]" : "[0]";
if (compat.shaderLanguage == GLSL_VULKAN) {
WRITE(p, "\n");
@@ -247,7 +253,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (useHWTransform && hasNormal)
WRITE(p, "layout (location = %d) in vec3 normal;\n", (int)PspAttributeLocation::NORMAL);
- if (!useHWTransform && enableFog)
+ if (!useHWTransform)
WRITE(p, "layout (location = %d) in float fog;\n", (int)PspAttributeLocation::NORMAL);
if (doTexture && hasTexcoord) {
@@ -272,11 +278,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, "layout (location = 0) out highp vec3 v_texcoord;\n");
}
- if (enableFog) {
- // See the fragment shader generator
- WRITE(p, "layout (location = 3) out highp float v_fogdepth;\n");
- }
-
+ WRITE(p, "layout (location = 3) out highp float v_fogdepth;\n");
WRITE(p, "invariant gl_Position;\n");
} else if (compat.shaderLanguage == HLSL_D3D11 || compat.shaderLanguage == HLSL_D3D9) {
@@ -295,9 +297,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, "float u_rotation : register(c%i);\n", CONST_VS_ROTATION);
}
- if (enableFog) {
- WRITE(p, "vec2 u_fogcoef : register(c%i);\n", CONST_VS_FOGCOEF);
- }
+ WRITE(p, "vec2 u_fogcoef : register(c%i);\n", CONST_VS_FOGCOEF);
if (useHWTransform || !hasColor)
WRITE(p, "vec4 u_matambientalpha : register(c%i);\n", CONST_VS_MATAMBIENTALPHA); // matambient + matalpha
@@ -319,6 +319,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (doTexture) {
WRITE(p, "vec4 u_uvscaleoffset : register(c%i);\n", CONST_VS_UVSCALEOFFSET);
}
+ // No need for light ubershader support here, D3D9 doesn't do it.
for (int i = 0; i < 4; i++) {
if (doLight[i] != LIGHT_OFF) {
// This is needed for shade mapping
@@ -397,9 +398,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (lmode) {
WRITE(p, " vec3 color1 : COLOR1;\n");
}
- if (enableFog) {
- WRITE(p, " float fog : NORMAL;\n");
- }
+ WRITE(p, " float fog : NORMAL;\n");
WRITE(p, "};\n");
}
@@ -412,15 +411,20 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (lmode)
WRITE(p, " vec3 v_color1 : COLOR1;\n");
- if (enableFog) {
- WRITE(p, " float v_fogdepth: TEXCOORD1;\n");
- }
+ WRITE(p, " float v_fogdepth : TEXCOORD1;\n");
if (compat.shaderLanguage == HLSL_D3D9) {
WRITE(p, " vec4 gl_Position : POSITION;\n");
} else {
WRITE(p, " vec4 gl_Position : SV_Position;\n");
- if (vertexRangeCulling && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) {
- WRITE(p, " float gl_ClipDistance : SV_ClipDistance0;\n");
+ bool clipRange = vertexRangeCulling && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE);
+ if (clipClampedDepth && clipRange) {
+ WRITE(p, " float2 gl_ClipDistance : SV_ClipDistance;\n");
+ clipClampedDepthSuffix = ".x";
+ vertexRangeClipSuffix = ".y";
+ } else if (clipClampedDepth || clipRange) {
+ WRITE(p, " float gl_ClipDistance : SV_ClipDistance;\n");
+ clipClampedDepthSuffix = "";
+ vertexRangeClipSuffix = "";
}
if (vertexRangeCulling && gstate_c.Supports(GPU_SUPPORTS_CULL_DISTANCE)) {
WRITE(p, " float2 gl_CullDistance : SV_CullDistance0;\n");
@@ -449,7 +453,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, "%s mediump vec3 normal;\n", compat.attribute);
*attrMask |= 1 << ATTR_NORMAL;
}
- if (!useHWTransform && enableFog) {
+ if (!useHWTransform) {
WRITE(p, "%s highp float fog;\n", compat.attribute);
*attrMask |= 1 << ATTR_NORMAL;
}
@@ -511,28 +515,32 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, "uniform vec4 u_uvscaleoffset;\n");
*uniformMask |= DIRTY_UVSCALEOFFSET;
}
+ if (lightUberShader) {
+ p.C("uniform uint u_lightControl;\n");
+ *uniformMask |= DIRTY_LIGHT_CONTROL;
+ }
for (int i = 0; i < 4; i++) {
- if (doLight[i] != LIGHT_OFF) {
+ if (lightUberShader || doLight[i] != LIGHT_OFF) {
// This is needed for shade mapping
WRITE(p, "uniform vec3 u_lightpos%i;\n", i);
*uniformMask |= DIRTY_LIGHT0 << i;
}
- if (doLight[i] == LIGHT_FULL) {
+ if (lightUberShader || doLight[i] == LIGHT_FULL) {
*uniformMask |= DIRTY_LIGHT0 << i;
GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2));
GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2));
- if (type != GE_LIGHTTYPE_DIRECTIONAL)
+ if (lightUberShader || type != GE_LIGHTTYPE_DIRECTIONAL)
WRITE(p, "uniform mediump vec3 u_lightatt%i;\n", i);
- if (type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) {
+ if (lightUberShader || type == GE_LIGHTTYPE_SPOT || type == GE_LIGHTTYPE_UNKNOWN) {
WRITE(p, "uniform mediump vec3 u_lightdir%i;\n", i);
WRITE(p, "uniform mediump vec2 u_lightangle_spotCoef%i;\n", i);
}
WRITE(p, "uniform lowp vec3 u_lightambient%i;\n", i);
WRITE(p, "uniform lowp vec3 u_lightdiffuse%i;\n", i);
- if (comp == GE_LIGHTCOMP_BOTH) {
+ if (lightUberShader || comp == GE_LIGHTCOMP_BOTH) {
WRITE(p, "uniform lowp vec3 u_lightspecular%i;\n", i);
}
}
@@ -540,7 +548,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (enableLighting) {
WRITE(p, "uniform lowp vec4 u_ambient;\n");
*uniformMask |= DIRTY_AMBIENT;
- if ((matUpdate & 2) == 0 || !hasColor) {
+ if (lightUberShader || (matUpdate & 2) == 0 || !hasColor) {
WRITE(p, "uniform lowp vec3 u_matdiffuse;\n");
*uniformMask |= DIRTY_MATDIFFUSE;
}
@@ -561,10 +569,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, "uniform lowp vec4 u_matambientalpha;\n"); // matambient + matalpha
*uniformMask |= DIRTY_MATAMBIENTALPHA;
}
- if (enableFog) {
- WRITE(p, "uniform highp vec2 u_fogcoef;\n");
- *uniformMask |= DIRTY_FOGCOEF;
- }
+ WRITE(p, "uniform highp vec2 u_fogcoef;\n");
+ *uniformMask |= DIRTY_FOGCOEF;
if (!isModeThrough) {
WRITE(p, "uniform highp vec4 u_depthRange;\n");
@@ -582,13 +588,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, "%s %s vec3 v_texcoord;\n", compat.varying_vs, highpTexcoord ? "highp" : "mediump");
}
- if (enableFog) {
- // See the fragment shader generator
- if (highpFog) {
- WRITE(p, "%s highp float v_fogdepth;\n", compat.varying_vs);
- } else {
- WRITE(p, "%s mediump float v_fogdepth;\n", compat.varying_vs);
- }
+ // See the fragment shader generator
+ if (highpFog) {
+ WRITE(p, "%s highp float v_fogdepth;\n", compat.varying_vs);
+ } else {
+ WRITE(p, "%s mediump float v_fogdepth;\n", compat.varying_vs);
}
}
@@ -716,7 +720,6 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, " vec4 basis_u = tess_weights_u[weight_idx.x].basis;\n");
WRITE(p, " vec4 basis_v = tess_weights_v[weight_idx.y].basis;\n");
WRITE(p, " mat4 basis = outerProduct(basis_u, basis_v);\n");
-
} else {
WRITE(p, " int index_u, index_v;\n");
for (int i = 0; i < 4; i++) {
@@ -799,7 +802,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
} else {
WRITE(p, " vec4 position = In.position;\n");
}
- if (!useHWTransform && enableFog) {
+ if (!useHWTransform) {
WRITE(p, " float fog = In.fog;\n");
}
if (enableBones) {
@@ -825,9 +828,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (lmode)
WRITE(p, " %sv_color1 = splat3(0.0);\n", compat.vsOutPrefix);
}
- if (enableFog) {
- WRITE(p, " %sv_fogdepth = fog;\n", compat.vsOutPrefix);
- }
+ WRITE(p, " %sv_fogdepth = fog;\n", compat.vsOutPrefix);
if (isModeThrough) {
// The proj_through matrix already has the rotation, if needed.
WRITE(p, " vec4 outPos = mul(u_proj_through, vec4(position.xyz, 1.0));\n");
@@ -944,14 +945,25 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
// TODO: Declare variables for dots for shade mapping if needed.
- const char *ambientStr = (matUpdate & 1) && hasColor ? "color0" : "u_matambientalpha";
- const char *diffuseStr = (matUpdate & 2) && hasColor ? "color0.rgb" : "u_matdiffuse";
- const char *specularStr = (matUpdate & 4) && hasColor ? "color0.rgb" : "u_matspecular.rgb";
+ const char *srcCol = "color0";
if (doBezier || doSpline) {
// TODO: Probably, should use hasColorTess but FF4 has a problem with drawing the background.
- ambientStr = (matUpdate & 1) && hasColor ? "tess.col" : "u_matambientalpha";
- diffuseStr = (matUpdate & 2) && hasColor ? "tess.col.rgb" : "u_matdiffuse";
- specularStr = (matUpdate & 4) && hasColor ? "tess.col.rgb" : "u_matspecular.rgb";
+ srcCol = "tess.col";
+ }
+
+ if (lightUberShader && hasColor) {
+ p.F(" vec4 ambientColor = ((u_lightControl & (1u << 20u)) != 0u) ? %s : u_matambientalpha;\n", srcCol);
+ if (enableLighting) {
+ p.F(" vec3 diffuseColor = ((u_lightControl & (1u << 21u)) != 0u) ? %s.rgb : u_matdiffuse;\n", srcCol);
+ p.F(" vec3 specularColor = ((u_lightControl & (1u << 22u)) != 0u) ? %s.rgb : u_matspecular.rgb;\n", srcCol);
+ }
+ } else {
+ // This path also takes care of the lightUberShader && !hasColor path, because all comparisons fail.
+ p.F(" vec4 ambientColor = %s;\n", (matUpdate & 1) && hasColor ? srcCol : "u_matambientalpha");
+ if (enableLighting) {
+ p.F(" vec3 diffuseColor = %s.rgb;\n", (matUpdate & 2) && hasColor ? srcCol : "u_matdiffuse");
+ p.F(" vec3 specularColor = %s.rgb;\n", (matUpdate & 4) && hasColor ? srcCol : "u_matspecular");
+ }
}
bool diffuseIsZero = true;
@@ -959,7 +971,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
bool distanceNeeded = false;
bool anySpots = false;
if (enableLighting) {
- WRITE(p, " lowp vec4 lightSum0 = u_ambient * %s + vec4(u_matemissive, 0.0);\n", ambientStr);
+ p.C(" lowp vec4 lightSum0 = u_ambient * ambientColor + vec4(u_matemissive, 0.0);\n");
for (int i = 0; i < 4; i++) {
GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4*i, 2));
@@ -975,6 +987,13 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
anySpots = true;
}
+ if (lightUberShader) {
+ anySpots = true;
+ diffuseIsZero = false;
+ specularIsZero = false;
+ distanceNeeded = true;
+ }
+
if (!specularIsZero) {
WRITE(p, " lowp vec3 lightSum1 = splat3(0.0);\n");
}
@@ -992,76 +1011,133 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
}
}
- // Calculate lights if needed. If shade mapping is enabled, lights may need to be
- // at least partially calculated.
- for (int i = 0; i < 4; i++) {
- if (doLight[i] != LIGHT_FULL)
- continue;
-
- GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4*i, 2));
- GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4*i, 2));
-
- if (type == GE_LIGHTTYPE_DIRECTIONAL) {
- // We prenormalize light positions for directional lights.
- WRITE(p, " toLight = u_lightpos%i;\n", i);
- } else {
- WRITE(p, " toLight = u_lightpos%i - worldpos;\n", i);
- WRITE(p, " distance = length(toLight);\n");
- WRITE(p, " toLight /= distance;\n");
+ if (lightUberShader) {
+ // TODO: Actually loop in the shader. For now, we write it all out.
+ // Will need to change how the data is stored to loop efficiently.
+ // u_lightControl is computed in PackLightControlBits().
+ for (int i = 0; i < 4; i++) {
+ p.F(" if ((u_lightControl & %du) != 0u) { \n", 1 << i);
+ p.F(" uint comp = (u_lightControl >> %d) & 3u;\n", 4 + 4 * i);
+ p.F(" uint type = (u_lightControl >> %d) & 3u;\n", 4 + 4 * i + 2);
+ p.C(" if (type == 0u) {\n"); // GE_LIGHTTYPE_DIRECTIONAL
+ p.F(" toLight = u_lightpos%d;\n", i);
+ p.C(" } else {\n");
+ p.F(" toLight = u_lightpos%d - worldpos;\n", i);
+ p.F(" distance = length(toLight);\n", i);
+ p.F(" toLight /= distance;\n", i);
+ p.C(" }\n");
+ p.C(" ldot = dot(toLight, worldnormal);\n");
+ p.C(" if (comp == 2u) {\n"); // GE_LIGHTCOMP_ONLYPOWDIFFUSE
+ p.C(" if (u_matspecular.a <= 0.0) {\n");
+ p.C(" ldot = 1.0;\n");
+ p.C(" } else {\n");
+ p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n");
+ p.C(" }\n");
+ p.C(" }\n");
+ p.C(" switch (type) {\n"); // Attenuation
+ p.C(" case 1u:\n"); // GE_LIGHTTYPE_POINT
+ p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i);
+ p.C(" break;\n");
+ p.C(" case 2u:\n"); // GE_LIGHTTYPE_SPOT
+ p.F(" angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i);
+ p.F(" if (angle >= u_lightangle_spotCoef%i.x) {\n", i);
+ p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i);
+ p.C(" } else {\n");
+ p.C(" lightScale = 0.0;\n");
+ p.C(" }\n");
+ p.C(" break;\n");
+ p.C(" default:\n"); // GE_LIGHTTYPE_DIRECTIONAL
+ p.C(" lightScale = 1.0;\n");
+ p.C(" break;\n");
+ p.C(" }\n");
+ p.F(" diffuse = (u_lightdiffuse%i * diffuseColor) * max(ldot, 0.0);\n", i);
+ p.C(" if (comp == 1u) {\n"); // do specular
+ p.C(" if (ldot >= 0.0) {\n");
+ p.C(" ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n");
+ p.C(" if (u_matspecular.a <= 0.0) {\n");
+ p.C(" ldot = 1.0;\n");
+ p.C(" } else {\n");
+ p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n");
+ p.C(" }\n");
+ p.C(" if (ldot > 0.0)\n");
+ p.F(" lightSum1 += u_lightspecular%i * specularColor * ldot * lightScale;\n", i);
+ p.C(" }\n");
+ p.C(" }\n");
+ p.F(" lightSum0.rgb += (u_lightambient%i * ambientColor.rgb + diffuse) * lightScale;\n", i);
+ p.C(" }\n");
}
+ } else {
+ // Calculate lights if needed. If shade mapping is enabled, lights may need to be
+ // at least partially calculated.
+ for (int i = 0; i < 4; i++) {
+ if (doLight[i] != LIGHT_FULL)
+ continue;
- bool doSpecular = comp == GE_LIGHTCOMP_BOTH;
- bool poweredDiffuse = comp == GE_LIGHTCOMP_ONLYPOWDIFFUSE;
+ GELightType type = static_cast(id.Bits(VS_BIT_LIGHT0_TYPE + 4 * i, 2));
+ GELightComputation comp = static_cast(id.Bits(VS_BIT_LIGHT0_COMP + 4 * i, 2));
- WRITE(p, " ldot = dot(toLight, worldnormal);\n");
- if (poweredDiffuse) {
- // pow(0.0, 0.0) may be undefined, but the PSP seems to treat it as 1.0.
- // Seen in Tales of the World: Radiant Mythology (#2424.)
- WRITE(p, " if (u_matspecular.a <= 0.0) {\n");
- WRITE(p, " ldot = 1.0;\n");
- WRITE(p, " } else {\n");
- WRITE(p, " ldot = pow(max(ldot, 0.0), u_matspecular.a);\n");
- WRITE(p, " }\n");
+ if (type == GE_LIGHTTYPE_DIRECTIONAL) {
+ // We prenormalize light positions for directional lights.
+ p.F(" toLight = u_lightpos%i;\n", i);
+ } else {
+ p.F(" toLight = u_lightpos%i - worldpos;\n", i);
+ p.C(" distance = length(toLight);\n");
+ p.C(" toLight /= distance;\n");
+ }
+
+ bool doSpecular = comp == GE_LIGHTCOMP_BOTH;
+ bool poweredDiffuse = comp == GE_LIGHTCOMP_ONLYPOWDIFFUSE;
+
+ p.C(" ldot = dot(toLight, worldnormal);\n");
+ if (poweredDiffuse) {
+ // pow(0.0, 0.0) may be undefined, but the PSP seems to treat it as 1.0.
+ // Seen in Tales of the World: Radiant Mythology (#2424.)
+ p.C(" if (u_matspecular.a <= 0.0) {\n");
+ p.C(" ldot = 1.0;\n");
+ p.C(" } else {\n");
+ p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n");
+ p.C(" }\n");
+ }
+
+ const char *timesLightScale = " * lightScale";
+
+ // Attenuation
+ switch (type) {
+ case GE_LIGHTTYPE_DIRECTIONAL:
+ timesLightScale = "";
+ break;
+ case GE_LIGHTTYPE_POINT:
+ p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i);
+ break;
+ case GE_LIGHTTYPE_SPOT:
+ case GE_LIGHTTYPE_UNKNOWN:
+ p.F(" angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i);
+ p.F(" if (angle >= u_lightangle_spotCoef%i.x) {\n", i);
+ p.F(" lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i);
+ p.C(" } else {\n");
+ p.C(" lightScale = 0.0;\n");
+ p.C(" }\n");
+ break;
+ default:
+ // ILLEGAL
+ break;
+ }
+
+ p.F(" diffuse = (u_lightdiffuse%i * diffuseColor) * max(ldot, 0.0);\n", i);
+ if (doSpecular) {
+ p.C(" if (ldot >= 0.0) {\n");
+ p.C(" ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n");
+ p.C(" if (u_matspecular.a <= 0.0) {\n");
+ p.C(" ldot = 1.0;\n");
+ p.C(" } else {\n");
+ p.C(" ldot = pow(max(ldot, 0.0), u_matspecular.a);\n");
+ p.C(" }\n");
+ p.C(" if (ldot > 0.0)\n");
+ p.F(" lightSum1 += u_lightspecular%i * specularColor * ldot %s;\n", i, timesLightScale);
+ p.C(" }\n");
+ }
+ p.F(" lightSum0.rgb += (u_lightambient%i * ambientColor.rgb + diffuse)%s;\n", i, timesLightScale);
}
-
- const char *timesLightScale = " * lightScale";
-
- // Attenuation
- switch (type) {
- case GE_LIGHTTYPE_DIRECTIONAL:
- timesLightScale = "";
- break;
- case GE_LIGHTTYPE_POINT:
- WRITE(p, " lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0);\n", i);
- break;
- case GE_LIGHTTYPE_SPOT:
- case GE_LIGHTTYPE_UNKNOWN:
- WRITE(p, " angle = length(u_lightdir%i) == 0.0 ? 0.0 : dot(normalize(u_lightdir%i), toLight);\n", i, i);
- WRITE(p, " if (angle >= u_lightangle_spotCoef%i.x) {\n", i);
- WRITE(p, " lightScale = clamp(1.0 / dot(u_lightatt%i, vec3(1.0, distance, distance*distance)), 0.0, 1.0) * (u_lightangle_spotCoef%i.y <= 0.0 ? 1.0 : pow(angle, u_lightangle_spotCoef%i.y));\n", i, i, i);
- WRITE(p, " } else {\n");
- WRITE(p, " lightScale = 0.0;\n");
- WRITE(p, " }\n");
- break;
- default:
- // ILLEGAL
- break;
- }
-
- WRITE(p, " diffuse = (u_lightdiffuse%i * %s) * max(ldot, 0.0);\n", i, diffuseStr);
- if (doSpecular) {
- WRITE(p, " if (ldot >= 0.0) {\n");
- WRITE(p, " ldot = dot(normalize(toLight + vec3(0.0, 0.0, 1.0)), worldnormal);\n");
- WRITE(p, " if (u_matspecular.a <= 0.0) {\n");
- WRITE(p, " ldot = 1.0;\n");
- WRITE(p, " } else {\n");
- WRITE(p, " ldot = pow(max(ldot, 0.0), u_matspecular.a);\n");
- WRITE(p, " }\n");
- WRITE(p, " if (ldot > 0.0)\n");
- WRITE(p, " lightSum1 += u_lightspecular%i * %s * ldot %s;\n", i, specularStr, timesLightScale);
- WRITE(p, " }\n");
- }
- WRITE(p, " lightSum0.rgb += (u_lightambient%i * %s.rgb + diffuse)%s;\n", i, ambientStr, timesLightScale);
}
if (enableLighting) {
@@ -1116,7 +1192,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
}
} else {
if (hasTexcoord) {
- WRITE(p, " %sv_texcoord = vec3(texcoord.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix);
+ if (doBezier || doSpline)
+ WRITE(p, " %sv_texcoord = vec3(tess.tex.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix);
+ else
+ WRITE(p, " %sv_texcoord = vec3(texcoord.xy * u_uvscaleoffset.xy + u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix);
} else {
WRITE(p, " %sv_texcoord = vec3(u_uvscaleoffset.zw, 0.0);\n", compat.vsOutPrefix);
}
@@ -1128,26 +1207,36 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
std::string temp_tc;
switch (uvProjMode) {
case GE_PROJMAP_POSITION: // Use model space XYZ as source
- temp_tc = "vec4(position, 1.0)";
+ if (doBezier || doSpline)
+ temp_tc = "vec4(tess.pos, 1.0)";
+ else
+ temp_tc = "vec4(position, 1.0)";
break;
case GE_PROJMAP_UV: // Use unscaled UV as source
{
// prescale is false here.
if (hasTexcoord) {
- temp_tc = "vec4(texcoord.xy, 0.0, 1.0)";
+ if (doBezier || doSpline)
+ temp_tc = "vec4(tess.tex.xy, 0.0, 1.0)";
+ else
+ temp_tc = "vec4(texcoord.xy, 0.0, 1.0)";
} else {
temp_tc = "vec4(0.0, 0.0, 0.0, 1.0)";
}
}
break;
case GE_PROJMAP_NORMALIZED_NORMAL: // Use normalized transformed normal as source
- if (hasNormal)
- temp_tc = StringFromFormat("length(normal) == 0.0 ? vec4(0.0, 0.0, 1.0, 1.0) : vec4(normalize(%snormal), 1.0)", flipNormal ? "-" : "");
+ if ((doBezier || doSpline) && hasNormalTess)
+ temp_tc = StringFromFormat("length(tess.nrm) == 0.0 ? vec4(0.0, 0.0, 0.0, 1.0) : vec4(normalize(%stess.nrm), 1.0)", flipNormalTess ? "-" : "");
+ else if (hasNormal)
+ temp_tc = StringFromFormat("length(normal) == 0.0 ? vec4(0.0, 0.0, 0.0, 1.0) : vec4(normalize(%snormal), 1.0)", flipNormal ? "-" : "");
else
temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)";
break;
case GE_PROJMAP_NORMAL: // Use non-normalized transformed normal as source
- if (hasNormal)
+ if ((doBezier || doSpline) && hasNormalTess)
+ temp_tc = flipNormalTess ? "vec4(-tess.nrm, 1.0)" : "vec4(tess.nrm, 1.0)";
+ else if (hasNormal)
temp_tc = flipNormal ? "vec4(-normal, 1.0)" : "vec4(normal, 1.0)";
else
temp_tc = "vec4(0.0, 0.0, 1.0, 1.0)";
@@ -1173,33 +1262,53 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
}
// Compute fogdepth
- if (enableFog)
- WRITE(p, " %sv_fogdepth = (viewPos.z + u_fogcoef.x) * u_fogcoef.y;\n", compat.vsOutPrefix);
+ WRITE(p, " %sv_fogdepth = (viewPos.z + u_fogcoef.x) * u_fogcoef.y;\n", compat.vsOutPrefix);
+ }
+
+ if (clipClampedDepth) {
+ // This should clip against minz, but only when it's above zero.
+ if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) {
+ // On OpenGL/GLES, these values account for the -1 -> 1 range.
+ WRITE(p, " if (u_depthRange.y - u_depthRange.x >= 1.0) {\n");
+ WRITE(p, " %sgl_ClipDistance%s = outPos.w + outPos.z;\n", compat.vsOutPrefix, clipClampedDepthSuffix);
+ } else {
+ // Everywhere else, it's 0 -> 1, simpler.
+ WRITE(p, " if (u_depthRange.y >= 1.0) {\n");
+ WRITE(p, " %sgl_ClipDistance%s = outPos.z;\n", compat.vsOutPrefix, clipClampedDepthSuffix);
+ }
+ // This is similar, but for maxz when it's below 65535.0. -1/0 don't matter here.
+ WRITE(p, " } else if (u_depthRange.x + u_depthRange.y <= 65534.0) {\n");
+ WRITE(p, " %sgl_ClipDistance%s = outPos.w - outPos.z;\n", compat.vsOutPrefix, clipClampedDepthSuffix);
+ WRITE(p, " } else {\n");
+ WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clipClampedDepthSuffix);
+ WRITE(p, " }\n");
}
if (vertexRangeCulling && !IsVRBuild()) {
WRITE(p, " vec3 projPos = outPos.xyz / outPos.w;\n");
WRITE(p, " float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n");
- // Vertex range culling doesn't happen when Z clips, note sign of w is important.
- WRITE(p, " if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n");
- const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y";
- const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y";
- WRITE(p, " if (%s || %s) {\n", outMin, outMax);
- WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n");
- WRITE(p, " }\n");
- WRITE(p, " }\n");
- WRITE(p, " if (u_cullRangeMin.w <= 0.0) {\n");
- WRITE(p, " if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n");
- WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n");
- WRITE(p, " }\n");
- WRITE(p, " }\n");
- const char *clip0 = compat.shaderLanguage == HLSL_D3D11 ? "" : "[0]";
+ if (!bugs.Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) {
+ // Vertex range culling doesn't happen when Z clips, note sign of w is important.
+ WRITE(p, " if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n");
+ const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y";
+ const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y";
+ WRITE(p, " if ((%s) || (%s)) {\n", outMin, outMax);
+ WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n");
+ WRITE(p, " }\n");
+ WRITE(p, " }\n");
+ WRITE(p, " if (u_cullRangeMin.w <= 0.0) {\n");
+ WRITE(p, " if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n");
+ WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n");
+ WRITE(p, " }\n");
+ WRITE(p, " }\n");
+ }
+
const char *cull0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]";
const char *cull1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]";
if (gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) {
- // TODO: Not rectangles...
- WRITE(p, " %sgl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", compat.vsOutPrefix, clip0);
+ // TODO: Ignore triangles from GE_PRIM_RECTANGLES in transform mode, which should not clip to neg z.
+ WRITE(p, " %sgl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", compat.vsOutPrefix, vertexRangeClipSuffix);
}
if (gstate_c.Supports(GPU_SUPPORTS_CULL_DISTANCE)) {
// Cull any triangle fully outside in the same direction when depth clamp enabled.
diff --git a/GPU/D3D11/DrawEngineD3D11.cpp b/GPU/D3D11/DrawEngineD3D11.cpp
index bc3c67da1d..eff16b543b 100644
--- a/GPU/D3D11/DrawEngineD3D11.cpp
+++ b/GPU/D3D11/DrawEngineD3D11.cpp
@@ -340,7 +340,7 @@ void DrawEngineD3D11::DoFlush() {
textureCache_->SetTexture();
gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
textureNeedsApply = true;
- } else if (gstate.getTextureAddress(0) == ((gstate.getFrameBufRawAddress() | 0x04000000) & 0x3FFFFFFF)) {
+ } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) {
// This catches the case of clearing a texture. (#10957)
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
}
diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp
index 3cfe0c4b37..bfbd12f190 100644
--- a/GPU/D3D11/GPU_D3D11.cpp
+++ b/GPU/D3D11/GPU_D3D11.cpp
@@ -82,7 +82,7 @@ GPU_D3D11::GPU_D3D11(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
// No need to flush before the tex scale/offset commands if we are baking
// the tex scale/offset into the vertices anyway.
UpdateCmdInfo();
- CheckGPUFeatures();
+ gstate_c.featureFlags = CheckGPUFeatures();
BuildReportingInfo();
@@ -100,40 +100,16 @@ GPU_D3D11::~GPU_D3D11() {
stockD3D11.Destroy();
}
-void GPU_D3D11::CheckGPUFeatures() {
- u32 features = 0;
-
- features |= GPU_SUPPORTS_BLEND_MINMAX;
+u32 GPU_D3D11::CheckGPUFeatures() const {
+ u32 features = GPUCommon::CheckGPUFeatures();
// Accurate depth is required because the Direct3D API does not support inverse Z.
// So we cannot incorrectly use the viewport transform as the depth range on Direct3D.
// TODO: Breaks text in PaRappa for some reason?
features |= GPU_SUPPORTS_ACCURATE_DEPTH;
-#ifndef _M_ARM
- // TODO: Do proper feature detection
- features |= GPU_SUPPORTS_ANISOTROPY;
-#endif
-
- features |= GPU_SUPPORTS_DEPTH_TEXTURE;
- features |= GPU_SUPPORTS_TEXTURE_NPOT;
- if (draw_->GetDeviceCaps().dualSourceBlend)
- features |= GPU_SUPPORTS_DUALSOURCE_BLEND;
if (draw_->GetDeviceCaps().depthClampSupported)
features |= GPU_SUPPORTS_DEPTH_CLAMP;
- if (draw_->GetDeviceCaps().clipDistanceSupported)
- features |= GPU_SUPPORTS_CLIP_DISTANCE;
- if (draw_->GetDeviceCaps().cullDistanceSupported)
- features |= GPU_SUPPORTS_CULL_DISTANCE;
- if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) {
- // Ignore the compat setting if clip and cull are both enabled.
- // When supported, we can do the depth side of range culling more correctly.
- const bool supported = draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported;
- const bool disabled = PSP_CoreParameter().compat.flags().DisableRangeCulling;
- if (supported || !disabled) {
- features |= GPU_SUPPORTS_VS_RANGE_CULLING;
- }
- }
features |= GPU_SUPPORTS_TEXTURE_FLOAT;
features |= GPU_SUPPORTS_INSTANCE_RENDERING;
@@ -146,10 +122,6 @@ void GPU_D3D11::CheckGPUFeatures() {
features |= GPU_SUPPORTS_16BIT_FORMATS;
}
- if (draw_->GetDeviceCaps().logicOpSupported) {
- features |= GPU_SUPPORTS_LOGIC_OP;
- }
-
if (!g_Config.bHighQualityDepth && (features & GPU_SUPPORTS_ACCURATE_DEPTH) != 0) {
features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT;
} else if (PSP_CoreParameter().compat.flags().PixelDepthRounding) {
@@ -164,11 +136,7 @@ void GPU_D3D11::CheckGPUFeatures() {
features |= GPU_USE_DEPTH_RANGE_HACK;
}
- if (PSP_CoreParameter().compat.flags().ClearToRAM) {
- features |= GPU_USE_CLEAR_RAM_HACK;
- }
-
- gstate_c.featureFlags = features;
+ return features;
}
// Needs to be called on GPU thread, not reporting thread.
@@ -206,7 +174,7 @@ void GPU_D3D11::BeginHostFrame() {
GPUCommon::BeginHostFrame();
UpdateCmdInfo();
if (resized_) {
- CheckGPUFeatures();
+ gstate_c.featureFlags = CheckGPUFeatures();
framebufferManager_->Resized();
drawEngine_.Resized();
textureCache_->NotifyConfigChanged();
diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h
index d1e5287782..e45c1ab79d 100644
--- a/GPU/D3D11/GPU_D3D11.h
+++ b/GPU/D3D11/GPU_D3D11.h
@@ -36,7 +36,7 @@ public:
GPU_D3D11(GraphicsContext *gfxCtx, Draw::DrawContext *draw);
~GPU_D3D11();
- void CheckGPUFeatures() override;
+ u32 CheckGPUFeatures() const override;
void PreExecuteOp(u32 op, u32 diff) override;
void ExecuteOp(u32 op, u32 diff) override;
diff --git a/GPU/D3D11/ShaderManagerD3D11.cpp b/GPU/D3D11/ShaderManagerD3D11.cpp
index a8537c8545..0560b52941 100644
--- a/GPU/D3D11/ShaderManagerD3D11.cpp
+++ b/GPU/D3D11/ShaderManagerD3D11.cpp
@@ -88,9 +88,11 @@ std::string D3D11VertexShader::GetShaderString(DebugShaderStringType type) const
}
}
+static constexpr size_t CODE_BUFFER_SIZE = 32768;
+
ShaderManagerD3D11::ShaderManagerD3D11(Draw::DrawContext *draw, ID3D11Device *device, ID3D11DeviceContext *context, D3D_FEATURE_LEVEL featureLevel)
: ShaderManagerCommon(draw), device_(device), context_(context), featureLevel_(featureLevel) {
- codeBuffer_ = new char[16384];
+ codeBuffer_ = new char[CODE_BUFFER_SIZE];
memset(&ub_base, 0, sizeof(ub_base));
memset(&ub_lights, 0, sizeof(ub_lights));
memset(&ub_bones, 0, sizeof(ub_bones));
@@ -211,6 +213,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertType, D3D11VertexShader **
uint32_t attrMask;
uint64_t uniformMask;
GenerateVertexShader(VSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &attrMask, &uniformMask, &genErrorString);
+ _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_));
vs = new D3D11VertexShader(device_, featureLevel_, VSID, codeBuffer_, vertType, useHWTransform);
vsCache_[VSID] = vs;
} else {
@@ -225,6 +228,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertType, D3D11VertexShader **
std::string genErrorString;
uint64_t uniformMask;
GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, nullptr, &genErrorString);
+ _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_));
fs = new D3D11FragmentShader(device_, featureLevel_, FSID, codeBuffer_, useHWTransform);
fsCache_[FSID] = fs;
} else {
diff --git a/GPU/D3D11/StateMappingD3D11.cpp b/GPU/D3D11/StateMappingD3D11.cpp
index 0cee52a155..2f86aa9914 100644
--- a/GPU/D3D11/StateMappingD3D11.cpp
+++ b/GPU/D3D11/StateMappingD3D11.cpp
@@ -153,15 +153,16 @@ void DrawEngineD3D11::ApplyDrawState(int prim) {
// We ignore the logicState on D3D since there's no support, the emulation of it is blend-and-shader only.
if (pipelineState_.FramebufferRead()) {
- bool fboTexNeedsBind = false;
- ApplyFramebufferRead(&fboTexNeedsBind);
+ FBOTexState fboTexBindState = FBO_TEX_NONE;
+ ApplyFramebufferRead(&fboTexBindState);
// The shader takes over the responsibility for blending, so recompute.
ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState);
- if (fboTexNeedsBind) {
+ if (fboTexBindState == FBO_TEX_COPY_BIND_TEX) {
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY);
// No sampler required, we do a plain Load in the pixel shader.
fboTexBound_ = true;
+ fboTexBindState = FBO_TEX_NONE;
framebufferManager_->RebindFramebuffer("RebindFramebuffer - ApplyDrawState");
// Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects.
@@ -432,7 +433,7 @@ void DrawEngineD3D11::ApplyDrawState(int prim) {
if (gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS) && !gstate.isModeClear() && gstate.isTextureMapEnabled()) {
textureCache_->SetTexture();
gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
- } else if (gstate.getTextureAddress(0) == ((gstate.getFrameBufRawAddress() | 0x04000000) & 0x3FFFFFFF)) {
+ } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) {
// This catches the case of clearing a texture.
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
}
diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp
index fb6cfba8b7..8d051c915c 100644
--- a/GPU/D3D11/TextureCacheD3D11.cpp
+++ b/GPU/D3D11/TextureCacheD3D11.cpp
@@ -65,6 +65,8 @@ Draw::DataFormat FromD3D11Format(u32 fmt) {
return Draw::DataFormat::A1R5G5B5_UNORM_PACK16;
case DXGI_FORMAT_B5G6R5_UNORM:
return Draw::DataFormat::R5G6B5_UNORM_PACK16;
+ case DXGI_FORMAT_R8_UNORM:
+ return Draw::DataFormat::R8_UNORM;
case DXGI_FORMAT_B8G8R8A8_UNORM:
default:
return Draw::DataFormat::R8G8B8A8_UNORM;
@@ -290,6 +292,8 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) {
dstFmt = ToDXGIFormat(plan.replaced->Format(plan.baseLevelSrc));
} else if (plan.scaleFactor > 1 || plan.saveTexture) {
dstFmt = DXGI_FORMAT_B8G8R8A8_UNORM;
+ } else if (plan.decodeToClut8) {
+ dstFmt = DXGI_FORMAT_R8_UNORM;
}
int levels;
@@ -449,20 +453,6 @@ DXGI_FORMAT TextureCacheD3D11::GetDestFormat(GETextureFormat format, GEPaletteFo
}
}
-CheckAlphaResult TextureCacheD3D11::CheckAlpha(const u32 *pixelData, u32 dstFmt, int w) {
- switch (dstFmt) {
- case DXGI_FORMAT_B4G4R4A4_UNORM:
- return CheckAlpha16((const u16 *)pixelData, w, 0xF000);
- case DXGI_FORMAT_B5G5R5A1_UNORM:
- return CheckAlpha16((const u16 *)pixelData, w, 0x8000);
- case DXGI_FORMAT_B5G6R5_UNORM:
- // Never has any alpha.
- return CHECKALPHA_FULL;
- default:
- return CheckAlpha32((const u32 *)pixelData, w, 0xFF000000);
- }
-}
-
bool TextureCacheD3D11::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) {
SetTexture();
if (!nextTexture_) {
@@ -525,3 +515,8 @@ bool TextureCacheD3D11::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level
stagingCopy->Release();
return true;
}
+
+void *TextureCacheD3D11::GetNativeTextureView(const TexCacheEntry *entry) {
+ ID3D11ShaderResourceView *textureView = DxView(entry);
+ return (void *)textureView;
+}
diff --git a/GPU/D3D11/TextureCacheD3D11.h b/GPU/D3D11/TextureCacheD3D11.h
index 03ec04a481..8b9d7caa78 100644
--- a/GPU/D3D11/TextureCacheD3D11.h
+++ b/GPU/D3D11/TextureCacheD3D11.h
@@ -61,10 +61,10 @@ protected:
void ReleaseTexture(TexCacheEntry *entry, bool delete_them) override;
void BindAsClutTexture(Draw::Texture *tex, bool smooth) override;
void ApplySamplingParams(const SamplerCacheKey &key) override;
+ void *GetNativeTextureView(const TexCacheEntry *entry) override;
private:
DXGI_FORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const;
- static CheckAlphaResult CheckAlpha(const u32 *pixelData, u32 dstFmt, int w);
void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override;
void BuildTexture(TexCacheEntry *const entry) override;
@@ -72,10 +72,10 @@ private:
ID3D11Device *device_;
ID3D11DeviceContext *context_;
- ID3D11Resource *&DxTex(TexCacheEntry *entry) {
+ ID3D11Resource *&DxTex(const TexCacheEntry *entry) {
return (ID3D11Resource *&)entry->texturePtr;
}
- ID3D11ShaderResourceView *DxView(TexCacheEntry *entry) {
+ ID3D11ShaderResourceView *DxView(const TexCacheEntry *entry) {
return (ID3D11ShaderResourceView *)entry->textureView;
}
diff --git a/GPU/Debugger/Breakpoints.cpp b/GPU/Debugger/Breakpoints.cpp
index 97fbca57ee..d1577d2b60 100644
--- a/GPU/Debugger/Breakpoints.cpp
+++ b/GPU/Debugger/Breakpoints.cpp
@@ -122,7 +122,7 @@ u32 GetAdjustedRenderTargetAddress(u32 op) {
switch (cmd) {
case GE_CMD_FRAMEBUFPTR:
case GE_CMD_ZBUFPTR:
- return op & 0x003FFFF0;
+ return op & 0x001FFFF0;
}
return (u32)-1;
@@ -278,7 +278,7 @@ bool IsRenderTargetBreakpoint(u32 addr, bool &temp) {
return false;
}
- addr &= 0x003FFFF0;
+ addr &= 0x001FFFF0;
std::lock_guard guard(breaksLock);
temp = breakRenderTargetsTemp.find(addr) != breakRenderTargetsTemp.end();
@@ -290,7 +290,7 @@ bool IsRenderTargetBreakpoint(u32 addr) {
return false;
}
- addr &= 0x003FFFF0;
+ addr &= 0x001FFFF0;
std::lock_guard guard(breaksLock);
return breakRenderTargets.find(addr) != breakRenderTargets.end();
@@ -339,7 +339,7 @@ void AddAddressBreakpoint(u32 addr, bool temp) {
} else {
// Remove the temporary marking.
breakPCsTemp.erase(addr);
- breakPCs.insert(std::make_pair(addr, BreakpointInfo{}));
+ breakPCs.emplace(addr, BreakpointInfo{});
}
breakPCsCount = breakPCs.size();
@@ -385,7 +385,7 @@ void AddTextureBreakpoint(u32 addr, bool temp) {
void AddRenderTargetBreakpoint(u32 addr, bool temp) {
std::lock_guard guard(breaksLock);
- addr &= 0x003FFFF0;
+ addr &= 0x001FFFF0;
if (temp) {
if (breakRenderTargets.find(addr) == breakRenderTargets.end()) {
@@ -444,7 +444,7 @@ void RemoveTextureBreakpoint(u32 addr) {
void RemoveRenderTargetBreakpoint(u32 addr) {
std::lock_guard guard(breaksLock);
- addr &= 0x003FFFF0;
+ addr &= 0x001FFFF0;
breakRenderTargetsTemp.erase(addr);
breakRenderTargets.erase(addr);
diff --git a/GPU/Debugger/Debugger.cpp b/GPU/Debugger/Debugger.cpp
index fb88fdca2f..fa55ec6b8e 100644
--- a/GPU/Debugger/Debugger.cpp
+++ b/GPU/Debugger/Debugger.cpp
@@ -18,6 +18,7 @@
#include
#include "Common/Log.h"
#include "Common/StringUtils.h"
+#include "Common/TimeUtil.h"
#include "GPU/GPU.h"
#include "GPU/Debugger/Breakpoints.h"
#include "GPU/Debugger/Debugger.h"
@@ -35,6 +36,8 @@ static int primsLastFrame = 0;
static int primsThisFrame = 0;
static int thisFlipNum = 0;
+static double lastStepTime = -1.0;
+
static std::vector> restrictPrimRanges;
static std::string restrictPrimRule;
@@ -56,6 +59,7 @@ void SetActive(bool flag) {
breakNext = BreakNext::NONE;
breakAtCount = -1;
GPUStepping::ResumeFromStepping();
+ lastStepTime = -1.0;
}
}
@@ -79,6 +83,7 @@ void SetBreakNext(BreakNext next) {
GPUBreakpoints::AddCmdBreakpoint(GE_CMD_SPLINE, true);
}
GPUStepping::ResumeFromStepping();
+ lastStepTime = next == BreakNext::NONE ? -1.0 : time_now_d();
}
void SetBreakCount(int c, bool relative) {
@@ -130,7 +135,12 @@ bool NotifyCommand(u32 pc) {
GPUBreakpoints::ClearTempBreakpoints();
auto info = gpuDebug->DissassembleOp(pc);
- NOTICE_LOG(G3D, "Waiting at %08x, %s", pc, info.desc.c_str());
+ if (lastStepTime >= 0.0) {
+ NOTICE_LOG(G3D, "Waiting at %08x, %s (%fms)", pc, info.desc.c_str(), (time_now_d() - lastStepTime) * 1000.0);
+ lastStepTime = -1.0;
+ } else {
+ NOTICE_LOG(G3D, "Waiting at %08x, %s", pc, info.desc.c_str());
+ }
GPUStepping::EnterStepping();
}
@@ -141,7 +151,12 @@ void NotifyDraw() {
if (!active)
return;
if (breakNext == BreakNext::DRAW && !GPUStepping::IsStepping()) {
- NOTICE_LOG(G3D, "Waiting at a draw");
+ if (lastStepTime >= 0.0) {
+ NOTICE_LOG(G3D, "Waiting at a draw (%fms)", (time_now_d() - lastStepTime) * 1000.0);
+ lastStepTime = -1.0;
+ } else {
+ NOTICE_LOG(G3D, "Waiting at a draw");
+ }
GPUStepping::EnterStepping();
}
}
@@ -204,9 +219,9 @@ bool SetRestrictPrims(const char *rule) {
// If there's nothing yet, add everything else.
if (updated.empty()) {
if (range.first > 0)
- updated.push_back(std::make_pair(0, range.first - 1));
+ updated.emplace_back(0, range.first - 1);
if (range.second < MAX_PRIMS)
- updated.push_back(std::make_pair(range.second + 1, MAX_PRIMS));
+ updated.emplace_back(range.second + 1, MAX_PRIMS);
continue;
}
@@ -225,7 +240,7 @@ bool SetRestrictPrims(const char *rule) {
// We're slicing a hole in this subrange.
int next = sub.second;
sub.second = range.first - 1;
- updated.push_back(std::make_pair(range.second + 1, next));
+ updated.emplace_back(range.second + 1, next);
continue;
}
diff --git a/GPU/Debugger/Playback.cpp b/GPU/Debugger/Playback.cpp
index aa34ce3ccf..0410a61c76 100644
--- a/GPU/Debugger/Playback.cpp
+++ b/GPU/Debugger/Playback.cpp
@@ -28,6 +28,7 @@
#include "Core/Config.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
+#include "Core/Debugger/MemBlockInfo.h"
#include "Core/ELF/ParamSFO.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/HLE/sceDisplay.h"
@@ -298,6 +299,7 @@ private:
void Registers(u32 ptr, u32 sz);
void Vertices(u32 ptr, u32 sz);
void Indices(u32 ptr, u32 sz);
+ void ClutAddr(u32 ptr, u32 sz);
void Clut(u32 ptr, u32 sz);
void TransferSrc(u32 ptr, u32 sz);
void Memset(u32 ptr, u32 sz);
@@ -306,8 +308,11 @@ private:
void Texture(int level, u32 ptr, u32 sz);
void Framebuf(int level, u32 ptr, u32 sz);
void Display(u32 ptr, u32 sz);
+ void EdramTrans(u32 ptr, u32 sz);
u32 execMemcpyDest = 0;
+ u32 execClutAddr = 0;
+ u32 execClutFlags = 0;
u32 execListBuf = 0;
u32 execListPos = 0;
u32 execListID = 0;
@@ -324,6 +329,10 @@ private:
};
void DumpExecute::SyncStall() {
+ if (execListBuf == 0) {
+ return;
+ }
+
gpu->UpdateStall(execListID, execListPos);
s64 listTicks = gpu->GetListTicks(execListID);
if (listTicks != -1) {
@@ -472,15 +481,39 @@ void DumpExecute::Indices(u32 ptr, u32 sz) {
execListQueue.push_back((GE_CMD_IADDR << 24) | (psp & 0x00FFFFFF));
}
-void DumpExecute::Clut(u32 ptr, u32 sz) {
- u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this));
- if (psp == 0) {
- ERROR_LOG(SYSTEM, "Unable to allocate for clut");
- return;
- }
+void DumpExecute::ClutAddr(u32 ptr, u32 sz) {
+ struct ClutAddrData {
+ u32 addr;
+ u32 flags;
+ };
+ const ClutAddrData *data = (const ClutAddrData *)(pushbuf_.data() + ptr);
+ execClutAddr = data->addr;
+ execClutFlags = data->flags;
+}
- execListQueue.push_back((GE_CMD_CLUTADDRUPPER << 24) | ((psp >> 8) & 0x00FF0000));
- execListQueue.push_back((GE_CMD_CLUTADDR << 24) | (psp & 0x00FFFFFF));
+void DumpExecute::Clut(u32 ptr, u32 sz) {
+ // This is always run when we have the actual address set.
+ if (execClutAddr != 0) {
+ const bool isTarget = (execClutFlags & 1) != 0;
+
+ // Could potentially always skip if !isTarget, but playing it safe for offset texture behavior.
+ if (Memory::IsValidRange(execClutAddr, sz) && (!isTarget || !g_Config.bSoftwareRendering)) {
+ // Intentionally don't trigger an upload here.
+ Memory::MemcpyUnchecked(execClutAddr, pushbuf_.data() + ptr, sz);
+ NotifyMemInfo(MemBlockFlags::WRITE, execClutAddr, sz, "ReplayClut");
+ }
+
+ execClutAddr = 0;
+ } else {
+ u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this));
+ if (psp == 0) {
+ ERROR_LOG(SYSTEM, "Unable to allocate for clut");
+ return;
+ }
+
+ execListQueue.push_back((GE_CMD_CLUTADDRUPPER << 24) | ((psp >> 8) & 0x00FF0000));
+ execListQueue.push_back((GE_CMD_CLUTADDR << 24) | (psp & 0x00FFFFFF));
+ }
}
void DumpExecute::TransferSrc(u32 ptr, u32 sz) {
@@ -522,6 +555,7 @@ void DumpExecute::Memcpy(u32 ptr, u32 sz) {
if (Memory::IsVRAMAddress(execMemcpyDest)) {
SyncStall();
Memory::MemcpyUnchecked(execMemcpyDest, pushbuf_.data() + ptr, sz);
+ NotifyMemInfo(MemBlockFlags::WRITE, execMemcpyDest, sz, "ReplayMemcpy");
gpu->PerformMemoryUpload(execMemcpyDest, sz);
}
}
@@ -572,6 +606,7 @@ void DumpExecute::Framebuf(int level, u32 ptr, u32 sz) {
if (Memory::IsValidRange(framebuf->addr, pspSize) && !unchangedVRAM && (!isTarget || !g_Config.bSoftwareRendering)) {
// Intentionally don't trigger an upload here.
Memory::MemcpyUnchecked(framebuf->addr, pushbuf_.data() + ptr + headerSize, pspSize);
+ NotifyMemInfo(MemBlockFlags::WRITE, framebuf->addr, pspSize, "ReplayTex");
}
}
@@ -590,6 +625,17 @@ void DumpExecute::Display(u32 ptr, u32 sz) {
__DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 0);
}
+void DumpExecute::EdramTrans(u32 ptr, u32 sz) {
+ uint32_t value;
+ memcpy(&value, pushbuf_.data() + ptr, 4);
+
+ // Sync up drawing.
+ SyncStall();
+
+ if (gpu)
+ gpu->SetAddrTranslation(value);
+}
+
DumpExecute::~DumpExecute() {
execMemcpyDest = 0;
if (execListBuf) {
@@ -601,6 +647,10 @@ DumpExecute::~DumpExecute() {
}
bool DumpExecute::Run() {
+ // Start with the default value.
+ if (gpu)
+ gpu->SetAddrTranslation(0x400);
+
for (const Command &cmd : commands_) {
switch (cmd.type) {
case CommandType::INIT:
@@ -619,6 +669,10 @@ bool DumpExecute::Run() {
Indices(cmd.ptr, cmd.sz);
break;
+ case CommandType::CLUTADDR:
+ ClutAddr(cmd.ptr, cmd.sz);
+ break;
+
case CommandType::CLUT:
Clut(cmd.ptr, cmd.sz);
break;
@@ -639,6 +693,10 @@ bool DumpExecute::Run() {
Memcpy(cmd.ptr, cmd.sz);
break;
+ case CommandType::EDRAMTRANS:
+ EdramTrans(cmd.ptr, cmd.sz);
+ break;
+
case CommandType::TEXTURE0:
case CommandType::TEXTURE1:
case CommandType::TEXTURE2:
diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp
index cc63dccf72..6058546b0f 100644
--- a/GPU/Debugger/Record.cpp
+++ b/GPU/Debugger/Record.cpp
@@ -37,6 +37,7 @@
#include "Core/MemMap.h"
#include "Core/System.h"
#include "Core/ThreadPools.h"
+#include "GPU/Common/GPUDebugInterface.h"
#include "GPU/GPUInterface.h"
#include "GPU/GPUState.h"
#include "GPU/ge_constants.h"
@@ -51,6 +52,7 @@ static bool active = false;
static bool nextFrame = false;
static int flipLastAction = -1;
static int flipFinishAt = -1;
+static uint32_t lastEdramTrans = 0x400;
static std::function writeCallback;
static std::vector pushbuf;
@@ -152,8 +154,19 @@ static void BeginRecording() {
u32 sz = 512 * 4;
pushbuf.resize(pushbuf.size() + sz);
gstate.Save((u32_le *)(pushbuf.data() + ptr));
-
commands.push_back({CommandType::INIT, sz, ptr});
+
+ // Also save the initial CLUT.
+ GPUDebugBuffer clut;
+ if (gpuDebug->GetCurrentClut(clut)) {
+ sz = clut.GetStride() * clut.PixelSize();
+ _assert_msg_(sz == 1024, "CLUT should be 1024 bytes");
+ ptr = (u32)pushbuf.size();
+ pushbuf.resize(pushbuf.size() + sz);
+ memcpy(pushbuf.data() + ptr, clut.GetData(), sz);
+ commands.push_back({ CommandType::CLUT, sz, ptr });
+ }
+
DirtyAllVRAM(DirtyVRAMFlag::DIRTY);
}
@@ -308,6 +321,40 @@ static Command EmitCommandWithRAM(CommandType t, const void *p, u32 sz, u32 alig
return cmd;
}
+static u32 GetTargetFlags(u32 addr, u32 sizeInRAM) {
+ addr &= 0x041FFFFF;
+ const bool isTarget = lastRenderTargets.find(addr) != lastRenderTargets.end();
+
+ bool isDirtyVRAM = false;
+ bool isDrawnVRAM = false;
+ uint32_t start = (addr >> DIRTY_VRAM_SHIFT) & DIRTY_VRAM_MASK;
+ uint32_t blocks = (sizeInRAM + DIRTY_VRAM_ROUND) >> DIRTY_VRAM_SHIFT;
+ bool startEven = (addr & DIRTY_VRAM_ROUND) == 0;
+ bool endEven = ((addr + sizeInRAM) & DIRTY_VRAM_ROUND) == 0;
+ for (uint32_t i = 0; i < blocks; ++i) {
+ DirtyVRAMFlag flag = dirtyVRAM[start + i];
+ isDirtyVRAM = isDirtyVRAM || flag != DirtyVRAMFlag::CLEAN;
+ isDrawnVRAM = isDrawnVRAM || flag == DirtyVRAMFlag::DRAWN;
+
+ // Mark the VRAM clean now that it's been copied to VRAM.
+ if (flag == DirtyVRAMFlag::DIRTY) {
+ if ((i > 0 || startEven) && (i < blocks || endEven))
+ dirtyVRAM[start + i] = DirtyVRAMFlag::CLEAN;
+ }
+ }
+
+ // The isTarget flag is mostly used for replay of dumps on a PSP.
+ u32 flags = isTarget ? 1 : 0;
+ // The unchangedVRAM flag tells us we can skip recopying.
+ if (!isDirtyVRAM)
+ flags |= 2;
+ // And the drawn flag tells us this data was potentially drawn to.
+ if (isDrawnVRAM)
+ flags |= 4;
+
+ return flags;
+}
+
static void EmitTextureData(int level, u32 texaddr) {
GETextureFormat format = gstate.getTextureFormat();
int w = gstate.getTextureWidth(level);
@@ -315,7 +362,6 @@ static void EmitTextureData(int level, u32 texaddr) {
int bufw = GetTextureBufw(level, texaddr, format);
int extraw = w > bufw ? w - bufw : 0;
u32 sizeInRAM = (textureBitsPerPixel[format] * (bufw * h + extraw)) / 8;
- const bool isTarget = lastRenderTargets.find(texaddr) != lastRenderTargets.end();
CommandType type = CommandType((int)CommandType::TEXTURE0 + level);
const u8 *p = Memory::GetPointerUnchecked(texaddr);
@@ -330,27 +376,7 @@ static void EmitTextureData(int level, u32 texaddr) {
u32 pad;
};
- bool isDirtyVRAM = false;
- bool isDrawnVRAM = false;
- uint32_t start = (texaddr >> DIRTY_VRAM_SHIFT) & DIRTY_VRAM_MASK;
- for (uint32_t i = 0; i < (sizeInRAM + DIRTY_VRAM_ROUND) >> DIRTY_VRAM_SHIFT; ++i) {
- DirtyVRAMFlag flag = dirtyVRAM[start + i];
- isDirtyVRAM = isDirtyVRAM || flag != DirtyVRAMFlag::CLEAN;
- isDrawnVRAM = isDrawnVRAM || flag == DirtyVRAMFlag::DRAWN;
-
- // Mark the VRAM clean now that it's been copied to VRAM.
- if (flag == DirtyVRAMFlag::DIRTY)
- dirtyVRAM[start + i] = DirtyVRAMFlag::CLEAN;
- }
-
- // The isTarget flag is mostly used for replay of dumps on a PSP.
- u32 flags = isTarget ? 1 : 0;
- // The unchangedVRAM flag tells us we can skip recopying.
- if (!isDirtyVRAM)
- flags |= 2;
- // And the drawn flag tells us this data was potentially drawn to.
- if (isDrawnVRAM)
- flags |= 4;
+ u32 flags = GetTargetFlags(texaddr, sizeInRAM);
FramebufData framebuf{ texaddr, bufw, flags };
framebufData.resize(sizeof(framebuf) + bytes);
memcpy(&framebufData[0], &framebuf, sizeof(framebuf));
@@ -456,12 +482,33 @@ static void EmitTransfer(u32 op) {
static void EmitClut(u32 op) {
u32 addr = gstate.getClutAddress();
+
+ // Hardware rendering may be using a framebuffer as CLUT.
+ // To get at this, we first run the command (normally we're called right before it has run.)
+ if (Memory::IsVRAMAddress(addr))
+ gpuDebug->SetCmdValue(op);
+
// Actually should only be 0x3F, but we allow enhanced CLUTs. See #15727.
u32 blocks = (op & 0x7F) == 0x40 ? 0x40 : (op & 0x3F);
u32 bytes = blocks * 32;
bytes = Memory::ValidSize(addr, bytes);
if (bytes != 0) {
+ // Send the original address so VRAM can be reasoned about.
+ if (Memory::IsVRAMAddress(addr)) {
+ struct ClutAddrData {
+ u32 addr;
+ u32 flags;
+ };
+ u32 flags = GetTargetFlags(addr, bytes);
+ ClutAddrData data{ addr, flags };
+
+ FlushRegisters();
+ Command cmd{CommandType::CLUTADDR, sizeof(data), (u32)pushbuf.size()};
+ pushbuf.resize(pushbuf.size() + sizeof(data));
+ memcpy(pushbuf.data() + cmd.ptr, &data, sizeof(data));
+ commands.push_back(cmd);
+ }
EmitCommandWithRAM(CommandType::CLUT, Memory::GetPointerUnchecked(addr), bytes, 16);
}
@@ -516,17 +563,35 @@ static void FinishRecording() {
active = false;
flipLastAction = gpuStats.numFlips;
flipFinishAt = -1;
+ lastEdramTrans = 0x400;
if (writeCallback)
writeCallback(filename);
writeCallback = nullptr;
}
+static void CheckEdramTrans() {
+ if (!gpuDebug)
+ return;
+
+ uint32_t value = gpuDebug->GetAddrTranslation();
+ if (value == lastEdramTrans)
+ return;
+ lastEdramTrans = value;
+
+ FlushRegisters();
+ Command cmd{CommandType::EDRAMTRANS, sizeof(value), (u32)pushbuf.size()};
+ pushbuf.resize(pushbuf.size() + sizeof(value));
+ memcpy(pushbuf.data() + cmd.ptr, &value, sizeof(value));
+ commands.push_back(cmd);
+}
+
void NotifyCommand(u32 pc) {
if (!active) {
return;
}
+ CheckEdramTrans();
const u32 op = Memory::Read_U32(pc);
const GECommand cmd = GECommand(op >> 24);
@@ -579,11 +644,14 @@ void NotifyMemcpy(u32 dest, u32 src, u32 sz) {
if (!active) {
return;
}
+
+ CheckEdramTrans();
if (Memory::IsVRAMAddress(dest)) {
FlushRegisters();
Command cmd{CommandType::MEMCPYDEST, sizeof(dest), (u32)pushbuf.size()};
pushbuf.resize(pushbuf.size() + sizeof(dest));
memcpy(pushbuf.data() + cmd.ptr, &dest, sizeof(dest));
+ commands.push_back(cmd);
sz = Memory::ValidSize(dest, sz);
if (sz != 0) {
@@ -597,6 +665,8 @@ void NotifyMemset(u32 dest, int v, u32 sz) {
if (!active) {
return;
}
+
+ CheckEdramTrans();
struct MemsetCommand {
u32 dest;
int value;
@@ -611,6 +681,7 @@ void NotifyMemset(u32 dest, int v, u32 sz) {
Command cmd{CommandType::MEMSET, sizeof(data), (u32)pushbuf.size()};
pushbuf.resize(pushbuf.size() + sizeof(data));
memcpy(pushbuf.data() + cmd.ptr, &data, sizeof(data));
+ commands.push_back(cmd);
DirtyVRAM(dest, sz, DirtyVRAMFlag::DIRTY);
}
}
@@ -619,9 +690,12 @@ void NotifyUpload(u32 dest, u32 sz) {
if (!active) {
return;
}
- NotifyMemcpy(dest, dest, sz);
- if (Memory::IsVRAMAddress(dest))
+
+ if (Memory::IsVRAMAddress(dest)) {
+ // This also checks the edram translation value.
+ NotifyMemcpy(dest, dest, sz);
DirtyVRAM(dest, sz, DirtyVRAMFlag::DIRTY);
+ }
}
static bool HasDrawCommands() {
@@ -656,6 +730,7 @@ void NotifyDisplay(u32 framebuf, int stride, int fmt) {
return;
}
+ CheckEdramTrans();
struct DisplayBufData {
PSPPointer topaddr;
int linesize, pixelFormat;
@@ -683,6 +758,7 @@ void NotifyBeginFrame() {
if (active && HasDrawCommands() && (noDisplayAction || gpuStats.numFlips == flipFinishAt)) {
NOTICE_LOG(SYSTEM, "Recording complete on frame");
+ CheckEdramTrans();
struct DisplayBufData {
PSPPointer topaddr;
u32 linesize, pixelFormat;
@@ -710,6 +786,10 @@ void NotifyBeginFrame() {
}
void NotifyCPU() {
+ if (!active) {
+ return;
+ }
+
DirtyAllVRAM(DirtyVRAMFlag::DIRTY);
}
diff --git a/GPU/Debugger/RecordFormat.h b/GPU/Debugger/RecordFormat.h
index 8079136d0c..295273eb0f 100644
--- a/GPU/Debugger/RecordFormat.h
+++ b/GPU/Debugger/RecordFormat.h
@@ -49,6 +49,8 @@ enum class CommandType : u8 {
MEMCPYDEST = 7,
MEMCPYDATA = 8,
DISPLAY = 9,
+ CLUTADDR = 10,
+ EDRAMTRANS = 11,
TEXTURE0 = 0x10,
TEXTURE1 = 0x11,
diff --git a/GPU/Directx9/DrawEngineDX9.cpp b/GPU/Directx9/DrawEngineDX9.cpp
index e05df6c98c..b8d4bfa0fb 100644
--- a/GPU/Directx9/DrawEngineDX9.cpp
+++ b/GPU/Directx9/DrawEngineDX9.cpp
@@ -327,7 +327,7 @@ void DrawEngineDX9::DoFlush() {
textureCache_->SetTexture();
gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
textureNeedsApply = true;
- } else if (gstate.getTextureAddress(0) == ((gstate.getFrameBufRawAddress() | 0x04000000) & 0x3FFFFFFF)) {
+ } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) {
// This catches the case of clearing a texture. (#10957)
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
}
diff --git a/GPU/Directx9/DrawEngineDX9.h b/GPU/Directx9/DrawEngineDX9.h
index a0ee23e60d..9ef5b37c65 100644
--- a/GPU/Directx9/DrawEngineDX9.h
+++ b/GPU/Directx9/DrawEngineDX9.h
@@ -170,6 +170,8 @@ private:
// Hardware tessellation
TessellationDataTransferDX9 *tessDataTransferDX9;
+ FBOTexState fboTexBindState_ = FBO_TEX_NONE;
+
int lastRenderStepId_ = -1;
bool fboTexNeedsBind_ = false;
diff --git a/GPU/Directx9/FramebufferManagerDX9.cpp b/GPU/Directx9/FramebufferManagerDX9.cpp
index f828336edd..f0281c400e 100644
--- a/GPU/Directx9/FramebufferManagerDX9.cpp
+++ b/GPU/Directx9/FramebufferManagerDX9.cpp
@@ -140,7 +140,7 @@
return;
}
- const u32 fb_address = vfb->fb_address & 0x3FFFFFFF;
+ const u32 fb_address = vfb->fb_address;
const int dstBpp = vfb->fb_format == GE_FORMAT_8888 ? 4 : 2;
// We always need to convert from the framebuffer native format.
@@ -365,8 +365,11 @@
}
if (!vfb) {
+ if (!Memory::IsValidAddress(fb_address))
+ return false;
// If there's no vfb and we're drawing there, must be memory?
- buffer = GPUDebugBuffer(Memory::GetPointerWrite(vfb->z_address), vfb->z_stride, 512, GPU_DBG_FORMAT_16BIT);
+ // TODO: Actually get the stencil.
+ buffer = GPUDebugBuffer(Memory::GetPointerWrite(fb_address), fb_stride, 512, GPU_DBG_FORMAT_8888);
return true;
}
diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp
index 3e5d0bac57..075d89e460 100644
--- a/GPU/Directx9/GPU_DX9.cpp
+++ b/GPU/Directx9/GPU_DX9.cpp
@@ -81,7 +81,7 @@ GPU_DX9::GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
// No need to flush before the tex scale/offset commands if we are baking
// the tex scale/offset into the vertices anyway.
UpdateCmdInfo();
- CheckGPUFeatures();
+ gstate_c.featureFlags = CheckGPUFeatures();
BuildReportingInfo();
@@ -98,68 +98,9 @@ GPU_DX9::GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
}
}
-// TODO: Move this detection elsewhere when it's needed elsewhere, not before. It's ugly.
-// Source: https://envytools.readthedocs.io/en/latest/hw/pciid.html#gf100
-enum NVIDIAGeneration {
- NV_PRE_KEPLER,
- NV_KEPLER,
- NV_MAXWELL,
- NV_PASCAL,
- NV_VOLTA,
- NV_TURING, // or later
-};
-
-static NVIDIAGeneration NVIDIAGetDeviceGeneration(int deviceID) {
- if (deviceID >= 0x1180 && deviceID <= 0x11bf)
- return NV_KEPLER; // GK104
- if (deviceID >= 0x11c0 && deviceID <= 0x11fa)
- return NV_KEPLER; // GK106
- if (deviceID >= 0x0fc0 && deviceID <= 0x0fff)
- return NV_KEPLER; // GK107
- if (deviceID >= 0x1003 && deviceID <= 0x1028)
- return NV_KEPLER; // GK110(B)
- if (deviceID >= 0x1280 && deviceID <= 0x12ba)
- return NV_KEPLER; // GK208
- if (deviceID >= 0x1381 && deviceID <= 0x13b0)
- return NV_MAXWELL; // GM107
- if (deviceID >= 0x1340 && deviceID <= 0x134d)
- return NV_MAXWELL; // GM108
- if (deviceID >= 0x13c0 && deviceID <= 0x13d9)
- return NV_MAXWELL; // GM204
- if (deviceID >= 0x1401 && deviceID <= 0x1427)
- return NV_MAXWELL; // GM206
- if (deviceID >= 0x15f7 && deviceID <= 0x15f9)
- return NV_PASCAL; // GP100
- if (deviceID >= 0x15f7 && deviceID <= 0x15f9)
- return NV_PASCAL; // GP100
- if (deviceID >= 0x1b00 && deviceID <= 0x1b38)
- return NV_PASCAL; // GP102
- if (deviceID >= 0x1b80 && deviceID <= 0x1be1)
- return NV_PASCAL; // GP104
- if (deviceID >= 0x1c02 && deviceID <= 0x1c62)
- return NV_PASCAL; // GP106
- if (deviceID >= 0x1c81 && deviceID <= 0x1c92)
- return NV_PASCAL; // GP107
- if (deviceID >= 0x1d01 && deviceID <= 0x1d12)
- return NV_PASCAL; // GP108
- if (deviceID >= 0x1d81 && deviceID <= 0x1dba)
- return NV_VOLTA; // GV100
- if (deviceID >= 0x1e02 && deviceID <= 0x1e3c)
- return NV_TURING; // TU102
- if (deviceID >= 0x1e82 && deviceID <= 0x1ed0)
- return NV_TURING; // TU104
- if (deviceID >= 0x1f02 && deviceID <= 0x1f51)
- return NV_TURING; // TU104
- if (deviceID >= 0x1e02)
- return NV_TURING; // More TU models or later, probably.
- return NV_PRE_KEPLER;
-}
-
-void GPU_DX9::CheckGPUFeatures() {
- u32 features = 0;
+u32 GPU_DX9::CheckGPUFeatures() const {
+ u32 features = GPUCommon::CheckGPUFeatures();
features |= GPU_SUPPORTS_16BIT_FORMATS;
- features |= GPU_SUPPORTS_BLEND_MINMAX;
- features |= GPU_SUPPORTS_DEPTH_TEXTURE;
features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL;
// Accurate depth is required because the Direct3D API does not support inverse Z.
@@ -168,41 +109,6 @@ void GPU_DX9::CheckGPUFeatures() {
features |= GPU_SUPPORTS_ACCURATE_DEPTH;
auto vendor = draw_->GetDeviceCaps().vendor;
- if (!PSP_CoreParameter().compat.flags().DisableRangeCulling) {
- // VS range culling (killing triangles in the vertex shader using NaN) causes problems on Intel.
- // Also causes problems on old NVIDIA.
- switch (vendor) {
- case Draw::GPUVendor::VENDOR_INTEL:
- break;
- case Draw::GPUVendor::VENDOR_NVIDIA:
- // Older NVIDIAs don't seem to like NaNs in their DX9 vertex shaders.
- // No idea if KEPLER is the right cutoff, but let's go with it.
- if (NVIDIAGetDeviceGeneration(draw_->GetDeviceCaps().deviceID) >= NV_KEPLER) {
- features |= GPU_SUPPORTS_VS_RANGE_CULLING;
- }
- break;
- default:
- features |= GPU_SUPPORTS_VS_RANGE_CULLING;
- break;
- }
- }
-
- D3DCAPS9 caps;
- ZeroMemory(&caps, sizeof(caps));
- HRESULT result = 0;
- if (deviceEx_) {
- result = deviceEx_->GetDeviceCaps(&caps);
- } else {
- result = device_->GetDeviceCaps(&caps);
- }
- if (FAILED(result)) {
- WARN_LOG_REPORT(G3D, "Direct3D9: Failed to get the device caps!");
- } else {
- if ((caps.RasterCaps & D3DPRASTERCAPS_ANISOTROPY) != 0 && caps.MaxAnisotropy > 1)
- features |= GPU_SUPPORTS_ANISOTROPY;
- if ((caps.TextureCaps & (D3DPTEXTURECAPS_NONPOW2CONDITIONAL | D3DPTEXTURECAPS_POW2)) == 0)
- features |= GPU_SUPPORTS_TEXTURE_NPOT;
- }
if (!g_Config.bHighQualityDepth) {
features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT;
@@ -213,11 +119,7 @@ void GPU_DX9::CheckGPUFeatures() {
features |= GPU_ROUND_DEPTH_TO_16BIT;
}
- if (PSP_CoreParameter().compat.flags().ClearToRAM) {
- features |= GPU_USE_CLEAR_RAM_HACK;
- }
-
- gstate_c.featureFlags = features;
+ return features;
}
GPU_DX9::~GPU_DX9() {
@@ -261,7 +163,7 @@ void GPU_DX9::BeginHostFrame() {
GPUCommon::BeginHostFrame();
UpdateCmdInfo();
if (resized_) {
- CheckGPUFeatures();
+ gstate_c.featureFlags = CheckGPUFeatures();
framebufferManager_->Resized();
drawEngine_.Resized();
shaderManagerDX9_->DirtyShader();
diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h
index 0e313da397..7c6a88d180 100644
--- a/GPU/Directx9/GPU_DX9.h
+++ b/GPU/Directx9/GPU_DX9.h
@@ -35,7 +35,7 @@ public:
GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw);
~GPU_DX9();
- void CheckGPUFeatures() override;
+ u32 CheckGPUFeatures() const override;
void PreExecuteOp(u32 op, u32 diff) override;
void ExecuteOp(u32 op, u32 diff) override;
diff --git a/GPU/Directx9/StateMappingDX9.cpp b/GPU/Directx9/StateMappingDX9.cpp
index 2ebadb3974..0dfa352f00 100644
--- a/GPU/Directx9/StateMappingDX9.cpp
+++ b/GPU/Directx9/StateMappingDX9.cpp
@@ -99,14 +99,14 @@ void DrawEngineDX9::ApplyDrawState(int prim) {
if (!gstate.isModeClear()) {
textureCache_->ApplyTexture();
- if (fboTexNeedsBind_) {
+ if (fboTexBindState_ = FBO_TEX_COPY_BIND_TEX) {
// Note that this is positions, not UVs, that we need the copy from.
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY);
// If we are rendering at a higher resolution, linear is probably best for the dest color.
device_->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
device_->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
fboTexBound_ = true;
- fboTexNeedsBind_ = false;
+ fboTexBindState_ = FBO_TEX_NONE;
}
// TODO: Test texture?
@@ -133,20 +133,23 @@ void DrawEngineDX9::ApplyDrawState(int prim) {
// We ignore the logicState on D3D since there's no support, the emulation of it is blend-and-shader only.
if (pipelineState_.FramebufferRead()) {
- bool fboTexNeedsBind = false;
- ApplyFramebufferRead(&fboTexNeedsBind);
+ ApplyFramebufferRead(&fboTexBindState_);
// The shader takes over the responsibility for blending, so recompute.
ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState);
- if (fboTexNeedsBind) {
+ if (fboTexBindState_ == FBO_TEX_COPY_BIND_TEX) {
// Note that this is positions, not UVs, that we need the copy from.
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY);
// If we are rendering at a higher resolution, linear is probably best for the dest color.
device_->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
device_->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
fboTexBound_ = true;
+ fboTexBindState_ = FBO_TEX_NONE;
dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE;
gstate_c.Dirty(DIRTY_BLEND_STATE);
+ } else if (fboTexBindState_ == FBO_TEX_READ_FRAMEBUFFER) {
+ // Not supported.
+ fboTexBindState_ = FBO_TEX_NONE;
}
dirtyRequiresRecheck_ |= DIRTY_FRAGMENTSHADER_STATE;
diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp
index bfb859bb40..9edc451a52 100644
--- a/GPU/Directx9/TextureCacheDX9.cpp
+++ b/GPU/Directx9/TextureCacheDX9.cpp
@@ -358,20 +358,6 @@ D3DFORMAT TextureCacheDX9::GetDestFormat(GETextureFormat format, GEPaletteFormat
}
}
-CheckAlphaResult TextureCacheDX9::CheckAlpha(const u32 *pixelData, u32 dstFmt, int w) {
- switch (dstFmt) {
- case D3DFMT_A4R4G4B4:
- return CheckAlpha16((const u16 *)pixelData, w, 0xF000);
- case D3DFMT_A1R5G5B5:
- return CheckAlpha16((const u16 *)pixelData, w, 0x8000);
- case D3DFMT_R5G6B5:
- // Never has any alpha.
- return CHECKALPHA_FULL;
- default:
- return CheckAlpha32(pixelData, w, 0xFF000000);
- }
-}
-
bool TextureCacheDX9::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) {
SetTexture();
ApplyTexture();
@@ -454,3 +440,8 @@ bool TextureCacheDX9::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level)
return success;
}
+
+void *TextureCacheDX9::GetNativeTextureView(const TexCacheEntry *entry) {
+ LPDIRECT3DBASETEXTURE9 tex = DxTex(entry);
+ return (void *)tex;
+}
diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h
index e8f4d9e58f..6a52eb2007 100644
--- a/GPU/Directx9/TextureCacheDX9.h
+++ b/GPU/Directx9/TextureCacheDX9.h
@@ -50,17 +50,17 @@ protected:
void Unbind() override;
void ReleaseTexture(TexCacheEntry *entry, bool delete_them) override;
void BindAsClutTexture(Draw::Texture *tex, bool smooth) override;
+ void *GetNativeTextureView(const TexCacheEntry *entry) override;
private:
void ApplySamplingParams(const SamplerCacheKey &key) override;
D3DFORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const;
- static CheckAlphaResult CheckAlpha(const u32 *pixelData, u32 dstFmt, int w);
void UpdateCurrentClut(GEPaletteFormat clutFormat, u32 clutBase, bool clutIndexIsSimple) override;
void BuildTexture(TexCacheEntry *const entry) override;
- LPDIRECT3DBASETEXTURE9 &DxTex(TexCacheEntry *entry) {
+ LPDIRECT3DBASETEXTURE9 &DxTex(const TexCacheEntry *entry) const {
return *(LPDIRECT3DBASETEXTURE9 *)&entry->texturePtr;
}
diff --git a/GPU/GLES/DepthBufferGLES.cpp b/GPU/GLES/DepthBufferGLES.cpp
index e5bcc20bf9..342ebdad60 100644
--- a/GPU/GLES/DepthBufferGLES.cpp
+++ b/GPU/GLES/DepthBufferGLES.cpp
@@ -118,7 +118,8 @@ void FramebufferManagerGLES::PackDepthbuffer(VirtualFramebuffer *vfb, int x, int
queries.push_back({ &u_depthDownloadTo8, "u_depthTo8" });
std::vector inits;
inits.push_back({ &u_depthDownloadTex, 0, TEX_SLOT_PSP_TEXTURE });
- depthDownloadProgram_ = render->CreateProgram(shaders, semantics, queries, inits, false, false);
+ GLRProgramFlags flags{};
+ depthDownloadProgram_ = render->CreateProgram(shaders, semantics, queries, inits, flags);
for (auto iter : shaders) {
render->DeleteShader(iter);
}
diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp
index 4eb23cd4d4..2102e46095 100644
--- a/GPU/GLES/DrawEngineGLES.cpp
+++ b/GPU/GLES/DrawEngineGLES.cpp
@@ -259,7 +259,7 @@ void DrawEngineGLES::DoFlush() {
textureCache_->SetTexture();
gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
textureNeedsApply = true;
- } else if (gstate.getTextureAddress(0) == ((gstate.getFrameBufRawAddress() | 0x04000000) & 0x3FFFFFFF)) {
+ } else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) {
// This catches the case of clearing a texture. (#10957)
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
}
@@ -500,7 +500,8 @@ void TessellationDataTransferGLES::SendDataToShader(const SimpleVertex *const *p
prevSizeU = size_u;
prevSizeV = size_v;
if (!data_tex[0])
- data_tex[0] = renderManager_->CreateTexture(GL_TEXTURE_2D, size_u * 3, size_v, 1, 1);
+ renderManager_->DeleteTexture(data_tex[0]);
+ data_tex[0] = renderManager_->CreateTexture(GL_TEXTURE_2D, size_u * 3, size_v, 1, 1);
renderManager_->TextureImage(data_tex[0], 0, size_u * 3, size_v, 1, Draw::DataFormat::R32G32B32A32_FLOAT, nullptr, GLRAllocType::NONE, false);
renderManager_->FinalizeTexture(data_tex[0], 0, false);
}
@@ -518,7 +519,8 @@ void TessellationDataTransferGLES::SendDataToShader(const SimpleVertex *const *p
if (prevSizeWU < weights.size_u) {
prevSizeWU = weights.size_u;
if (!data_tex[1])
- data_tex[1] = renderManager_->CreateTexture(GL_TEXTURE_2D, weights.size_u * 2, 1, 1, 1);
+ renderManager_->DeleteTexture(data_tex[1]);
+ data_tex[1] = renderManager_->CreateTexture(GL_TEXTURE_2D, weights.size_u * 2, 1, 1, 1);
renderManager_->TextureImage(data_tex[1], 0, weights.size_u * 2, 1, 1, Draw::DataFormat::R32G32B32A32_FLOAT, nullptr, GLRAllocType::NONE, false);
renderManager_->FinalizeTexture(data_tex[1], 0, false);
}
@@ -529,7 +531,8 @@ void TessellationDataTransferGLES::SendDataToShader(const SimpleVertex *const *p
if (prevSizeWV < weights.size_v) {
prevSizeWV = weights.size_v;
if (!data_tex[2])
- data_tex[2] = renderManager_->CreateTexture(GL_TEXTURE_2D, weights.size_v * 2, 1, 1, 1);
+ renderManager_->DeleteTexture(data_tex[2]);
+ data_tex[2] = renderManager_->CreateTexture(GL_TEXTURE_2D, weights.size_v * 2, 1, 1, 1);
renderManager_->TextureImage(data_tex[2], 0, weights.size_v * 2, 1, 1, Draw::DataFormat::R32G32B32A32_FLOAT, nullptr, GLRAllocType::NONE, false);
renderManager_->FinalizeTexture(data_tex[2], 0, false);
}
diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp
index 75fd0080b5..abae5134f9 100644
--- a/GPU/GLES/GPU_GLES.cpp
+++ b/GPU/GLES/GPU_GLES.cpp
@@ -54,7 +54,7 @@
GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
: GPUCommon(gfxCtx, draw), drawEngine_(draw), fragmentTestCache_(draw) {
UpdateVsyncInterval(true);
- CheckGPUFeatures();
+ gstate_c.featureFlags = CheckGPUFeatures();
shaderManagerGL_ = new ShaderManagerGLES(draw);
framebufferManagerGL_ = new FramebufferManagerGLES(draw);
@@ -148,42 +148,17 @@ GPU_GLES::~GPU_GLES() {
// Take the raw GL extension and versioning data and turn into feature flags.
// TODO: This should use DrawContext::GetDeviceCaps() more and more, and eventually
// this can be shared between all the backends.
-void GPU_GLES::CheckGPUFeatures() {
- u32 features = 0;
+u32 GPU_GLES::CheckGPUFeatures() const {
+ u32 features = GPUCommon::CheckGPUFeatures();
features |= GPU_SUPPORTS_16BIT_FORMATS;
- if (draw_->GetDeviceCaps().dualSourceBlend) {
- if (!g_Config.bVendorBugChecksEnabled || !draw_->GetBugs().Has(Draw::Bugs::DUAL_SOURCE_BLENDING_BROKEN)) {
- features |= GPU_SUPPORTS_DUALSOURCE_BLEND;
- }
- }
-
- if (gl_extensions.EXT_shader_framebuffer_fetch || gl_extensions.ARM_shader_framebuffer_fetch) {
- // This has caused problems in the past. Let's only enable on GLES3.
- if (gl_extensions.GLES3) {
- features |= GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH;
- }
- }
-
if ((gl_extensions.gpuVendor == GPU_VENDOR_NVIDIA) || (gl_extensions.gpuVendor == GPU_VENDOR_AMD))
features |= GPU_PREFER_REVERSE_COLOR_ORDER;
- if (draw_->GetDeviceCaps().textureNPOTFullySupported)
- features |= GPU_SUPPORTS_TEXTURE_NPOT;
-
- if (gl_extensions.EXT_blend_minmax)
- features |= GPU_SUPPORTS_BLEND_MINMAX;
-
- if (draw_->GetDeviceCaps().logicOpSupported)
- features |= GPU_SUPPORTS_LOGIC_OP;
-
if (gl_extensions.GLES3 || !gl_extensions.IsGLES)
features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL;
- if (draw_->GetDeviceCaps().anisoSupported)
- features |= GPU_SUPPORTS_ANISOTROPY;
-
bool canUseInstanceID = gl_extensions.EXT_draw_instanced || gl_extensions.ARB_draw_instanced;
bool canDefInstanceID = gl_extensions.IsGLES || gl_extensions.EXT_gpu_shader4 || gl_extensions.VersionGEThan(3, 1);
bool instanceRendering = gl_extensions.GLES3 || (canUseInstanceID && canDefInstanceID);
@@ -202,21 +177,6 @@ void GPU_GLES::CheckGPUFeatures() {
// Our implementation of depth texturing needs simple Z range, so can't
// use the extension hacks (yet).
}
- if (draw_->GetDeviceCaps().textureDepthSupported)
- features |= GPU_SUPPORTS_DEPTH_TEXTURE;
- if (draw_->GetDeviceCaps().clipDistanceSupported)
- features |= GPU_SUPPORTS_CLIP_DISTANCE;
- if (draw_->GetDeviceCaps().cullDistanceSupported)
- features |= GPU_SUPPORTS_CULL_DISTANCE;
- if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) {
- // Ignore the compat setting if clip and cull are both enabled.
- // When supported, we can do the depth side of range culling more correctly.
- const bool supported = draw_->GetDeviceCaps().clipDistanceSupported && draw_->GetDeviceCaps().cullDistanceSupported;
- const bool disabled = PSP_CoreParameter().compat.flags().DisableRangeCulling;
- if (supported || !disabled) {
- features |= GPU_SUPPORTS_VS_RANGE_CULLING;
- }
- }
// If we already have a 16-bit depth buffer, we don't need to round.
bool prefer24 = draw_->GetDeviceCaps().preferredDepthBufferFormat == Draw::DataFormat::D24_S8;
@@ -245,11 +205,7 @@ void GPU_GLES::CheckGPUFeatures() {
features |= GPU_USE_DEPTH_RANGE_HACK;
}
- if (PSP_CoreParameter().compat.flags().ClearToRAM) {
- features |= GPU_USE_CLEAR_RAM_HACK;
- }
-
- gstate_c.featureFlags = features;
+ return features;
}
bool GPU_GLES::IsReady() {
@@ -321,7 +277,7 @@ void GPU_GLES::BeginHostFrame() {
GPUCommon::BeginHostFrame();
UpdateCmdInfo();
if (resized_) {
- CheckGPUFeatures();
+ gstate_c.featureFlags = CheckGPUFeatures();
framebufferManager_->Resized();
drawEngine_.Resized();
shaderManagerGL_->DirtyShader();
diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h
index cbe6bc00b0..98bb1d9362 100644
--- a/GPU/GLES/GPU_GLES.h
+++ b/GPU/GLES/GPU_GLES.h
@@ -38,7 +38,7 @@ public:
~GPU_GLES();
// This gets called on startup and when we get back from settings.
- void CheckGPUFeatures() override;
+ u32 CheckGPUFeatures() const override;
bool IsReady() override;
void CancelReady() override;
diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp
index 4d37ae2ba6..a1f37a12f4 100644
--- a/GPU/GLES/ShaderManagerGLES.cpp
+++ b/GPU/GLES/ShaderManagerGLES.cpp
@@ -84,6 +84,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
std::vector semantics;
+ semantics.reserve(7);
semantics.push_back({ ATTR_POSITION, "position" });
semantics.push_back({ ATTR_TEXCOORD, "texcoord" });
if (useHWTransform_)
@@ -152,6 +153,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
queries.push_back({ &u_uvscaleoffset, "u_uvscaleoffset" });
queries.push_back({ &u_texclamp, "u_texclamp" });
queries.push_back({ &u_texclampoff, "u_texclampoff" });
+ queries.push_back({ &u_lightControl, "u_lightControl" });
for (int i = 0; i < 4; i++) {
static const char * const lightPosNames[4] = { "u_lightpos0", "u_lightpos1", "u_lightpos2", "u_lightpos3", };
@@ -184,6 +186,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
availableUniforms = vs->GetUniformMask() | fs->GetUniformMask();
std::vector initialize;
+ initialize.reserve(7);
initialize.push_back({ &u_tex, 0, TEX_SLOT_PSP_TEXTURE });
initialize.push_back({ &u_fbotex, 0, TEX_SLOT_SHADERBLEND_SRC });
initialize.push_back({ &u_testtex, 0, TEX_SLOT_ALPHATEST });
@@ -192,9 +195,17 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
initialize.push_back({ &u_tess_weights_u, 0, TEX_SLOT_SPLINE_WEIGHTS_U });
initialize.push_back({ &u_tess_weights_v, 0, TEX_SLOT_SPLINE_WEIGHTS_V });
- bool useDualSource = (gstate_c.featureFlags & GPU_SUPPORTS_DUALSOURCE_BLEND) != 0;
- bool useClip0 = VSID.Bit(VS_BIT_VERTEX_RANGE_CULLING) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE);
- program = render->CreateProgram(shaders, semantics, queries, initialize, useDualSource, useClip0);
+ GLRProgramFlags flags{};
+ flags.supportDualSource = (gstate_c.featureFlags & GPU_SUPPORTS_DUALSOURCE_BLEND) != 0;
+ if (!VSID.Bit(VS_BIT_IS_THROUGH) && gstate_c.Supports(GPU_SUPPORTS_DEPTH_CLAMP)) {
+ flags.useClipDistance0 = true;
+ if (VSID.Bit(VS_BIT_VERTEX_RANGE_CULLING) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE))
+ flags.useClipDistance1 = true;
+ } else if (VSID.Bit(VS_BIT_VERTEX_RANGE_CULLING) && gstate_c.Supports(GPU_SUPPORTS_CLIP_DISTANCE)) {
+ flags.useClipDistance0 = true;
+ }
+
+ program = render->CreateProgram(shaders, semantics, queries, initialize, flags);
// The rest, use the "dirty" mechanism.
dirtyUniforms = DIRTY_ALL_UNIFORMS;
@@ -354,6 +365,7 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu
if (IsVRBuild()) {
dirty |= DIRTY_VIEWMATRIX;
+ SetVRCompat(VR_COMPAT_FOG_COLOR, gstate.fogcolor);
}
if (!dirty)
return;
@@ -437,7 +449,7 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu
SetColorUniform3Alpha255(render_, &u_alphacolorref, gstate.getColorTestRef(), gstate.getAlphaTestRef() & gstate.getAlphaTestMask());
}
if (dirty & DIRTY_ALPHACOLORMASK) {
- SetColorUniform3iAlpha(render_, &u_alphacolormask, gstate.colortestmask, gstate.getAlphaTestMask());
+ render_->SetUniformUI1(&u_alphacolormask, gstate.getColorTestMask() | (gstate.getAlphaTestMask() << 24));
}
if (dirty & DIRTY_COLORWRITEMASK) {
render_->SetUniformUI1(&u_colorWriteMask, ~((gstate.pmska << 24) | (gstate.pmskc & 0xFFFFFF)));
@@ -461,7 +473,6 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu
}
render_->SetUniformF(&u_fogcoef, 2, fogcoef);
}
-
if (dirty & DIRTY_UVSCALEOFFSET) {
const float invW = 1.0f / (float)gstate_c.curTextureWidth;
const float invH = 1.0f / (float)gstate_c.curTextureHeight;
@@ -595,6 +606,9 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu
}
// Lighting
+ if (dirty & DIRTY_LIGHT_CONTROL) {
+ render_->SetUniformUI1(&u_lightControl, PackLightControlBits());
+ }
if (dirty & DIRTY_AMBIENT) {
SetColorUniform3Alpha(render_, &u_ambient, gstate.ambientcolor, gstate.getAmbientA());
}
@@ -647,10 +661,12 @@ void LinkedShader::UpdateUniforms(u32 vertType, const ShaderID &vsid, bool useBu
}
}
+static constexpr size_t CODE_BUFFER_SIZE = 32768;
+
ShaderManagerGLES::ShaderManagerGLES(Draw::DrawContext *draw)
: ShaderManagerCommon(draw), fsCache_(16), vsCache_(16) {
render_ = (GLRenderManager *)draw->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
- codeBuffer_ = new char[16384];
+ codeBuffer_ = new char[CODE_BUFFER_SIZE];
lastFSID_.set_invalid();
lastVSID_.set_invalid();
}
@@ -713,6 +729,7 @@ Shader *ShaderManagerGLES::CompileFragmentShader(FShaderID FSID) {
ERROR_LOG(G3D, "Shader gen error: %s", errorString.c_str());
return nullptr;
}
+ _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_));
std::string desc = FragmentShaderDesc(FSID);
ShaderDescGLES params{ GL_FRAGMENT_SHADER, 0, uniformMask };
return new Shader(render_, codeBuffer_, desc, params);
@@ -727,6 +744,7 @@ Shader *ShaderManagerGLES::CompileVertexShader(VShaderID VSID) {
ERROR_LOG(G3D, "Shader gen error: %s", errorString.c_str());
return nullptr;
}
+ _assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_));
std::string desc = VertexShaderDesc(VSID);
ShaderDescGLES params{ GL_VERTEX_SHADER, attrMask, uniformMask };
params.useHWTransform = useHWTransform;
@@ -828,7 +846,6 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs,
if (ls == nullptr) {
_dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE));
_dbg_assert_(FSID.Bit(FS_BIT_DO_TEXTURE) == VSID.Bit(VS_BIT_DO_TEXTURE));
- _dbg_assert_(FSID.Bit(FS_BIT_ENABLE_FOG) == VSID.Bit(VS_BIT_ENABLE_FOG));
_dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE));
// Check if we can link these.
@@ -981,7 +998,7 @@ void ShaderManagerGLES::Load(const Path &filename) {
if (!f.ReadArray(&fsid, 1)) {
return;
}
- diskCachePending_.link.push_back(std::make_pair(vsid, fsid));
+ diskCachePending_.link.emplace_back(vsid, fsid);
}
// Actual compilation happens in ContinuePrecompile(), called by GPU_GLES's IsReady.
diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h
index 101326ee9b..2dd715cc0b 100644
--- a/GPU/GLES/ShaderManagerGLES.h
+++ b/GPU/GLES/ShaderManagerGLES.h
@@ -97,6 +97,7 @@ public:
int u_texclampoff;
// Lighting
+ int u_lightControl;
int u_ambient;
int u_matambientalpha;
int u_matdiffuse;
diff --git a/GPU/GLES/StateMappingGLES.cpp b/GPU/GLES/StateMappingGLES.cpp
index dbfc115a91..cdcc9e5069 100644
--- a/GPU/GLES/StateMappingGLES.cpp
+++ b/GPU/GLES/StateMappingGLES.cpp
@@ -149,13 +149,14 @@ void DrawEngineGLES::ApplyDrawState(int prim) {
GenericLogicState &logicState = pipelineState_.logicState;
if (pipelineState_.FramebufferRead()) {
- bool fboTexNeedsBind = false;
- ApplyFramebufferRead(&fboTexNeedsBind);
+ FBOTexState fboTexBindState = FBO_TEX_NONE;
+ ApplyFramebufferRead(&fboTexBindState);
// The shader takes over the responsibility for blending, so recompute.
ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState);
// We copy the framebuffer here, as doing so will wipe any blend state if we do it later.
- if (fboTexNeedsBind) {
+ // fboTexNeedsBind_ won't be set if we can read directly from the target.
+ if (fboTexBindState == FBO_TEX_COPY_BIND_TEX) {
// Note that this is positions, not UVs, that we need the copy from.
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY);
// If we are rendering at a higher resolution, linear is probably best for the dest color.
@@ -166,6 +167,9 @@ void DrawEngineGLES::ApplyDrawState(int prim) {
// Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects.
dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE;
gstate_c.Dirty(DIRTY_BLEND_STATE);
+ } else if (fboTexBindState == FBO_TEX_READ_FRAMEBUFFER) {
+ // No action needed here.
+ fboTexBindState = FBO_TEX_NONE;
}
dirtyRequiresRecheck_ |= DIRTY_FRAGMENTSHADER_STATE;
gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE);
diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp
index c28fc6d1e5..83309208b8 100644
--- a/GPU/GLES/TextureCacheGLES.cpp
+++ b/GPU/GLES/TextureCacheGLES.cpp
@@ -266,6 +266,8 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) {
dstFmt = plan.replaced->Format(plan.baseLevelSrc);
} else if (plan.scaleFactor > 1 || plan.saveTexture) {
dstFmt = Draw::DataFormat::R8G8B8A8_UNORM;
+ } else if (plan.decodeToClut8) {
+ dstFmt = Draw::DataFormat::R8_UNORM;
}
if (plan.depth == 1) {
@@ -313,7 +315,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) {
if (plan.scaleFactor > 1) {
bpp = 4;
} else {
- bpp = dstFmt == Draw::DataFormat::R8G8B8A8_UNORM ? 4 : 2;
+ bpp = (int)Draw::DataFormatSizeInBytes(dstFmt);
}
}
@@ -335,7 +337,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) {
render_->FinalizeTexture(entry->textureName, plan.levelsToLoad, genMips);
} else {
- int bpp = dstFmt == Draw::DataFormat::R8G8B8A8_UNORM ? 4 : 2;
+ int bpp = (int)Draw::DataFormatSizeInBytes(dstFmt);
int stride = bpp * (plan.w * plan.scaleFactor);
int levelStride = stride * (plan.h * plan.scaleFactor);
@@ -443,3 +445,8 @@ void TextureCacheGLES::DeviceRestore(Draw::DrawContext *draw) {
render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
textureShaderCache_->DeviceRestore(draw);
}
+
+void *TextureCacheGLES::GetNativeTextureView(const TexCacheEntry *entry) {
+ GLRTexture *tex = entry->textureName;
+ return (void *)tex;
+}
diff --git a/GPU/GLES/TextureCacheGLES.h b/GPU/GLES/TextureCacheGLES.h
index 1eae540b87..2a4d04574d 100644
--- a/GPU/GLES/TextureCacheGLES.h
+++ b/GPU/GLES/TextureCacheGLES.h
@@ -66,6 +66,7 @@ protected:
void ReleaseTexture(TexCacheEntry *entry, bool delete_them) override;
void BindAsClutTexture(Draw::Texture *tex, bool smooth) override;
+ void *GetNativeTextureView(const TexCacheEntry *entry) override;
private:
void ApplySamplingParams(const SamplerCacheKey &key) override;
diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj
index 0480b490d7..808ce53635 100644
--- a/GPU/GPU.vcxproj
+++ b/GPU/GPU.vcxproj
@@ -340,6 +340,7 @@
+
@@ -455,6 +456,7 @@
+
diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters
index bd28a45e97..e110c20b8b 100644
--- a/GPU/GPU.vcxproj.filters
+++ b/GPU/GPU.vcxproj.filters
@@ -261,6 +261,9 @@
Debugger
+
+ Common
+
@@ -512,6 +515,9 @@
Debugger
+
+ Common
+
diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp
index 107398b7c8..9830984739 100644
--- a/GPU/GPUCommon.cpp
+++ b/GPU/GPUCommon.cpp
@@ -87,9 +87,9 @@ const CommonCommandTableEntry commonCommandTable[] = {
{ GE_CMD_FOG2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOEF },
// These affect the fragment shader so need flushing.
- { GE_CMD_CLEARMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
- { GE_CMD_TEXTUREMAPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
- { GE_CMD_FOGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE},
+ { GE_CMD_CLEARMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE },
+ { GE_CMD_TEXTUREMAPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE },
+ { GE_CMD_FOGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE },
{ GE_CMD_TEXMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE },
{ GE_CMD_TEXSHADELS, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
// Raster state for Direct3D 9, uncommon.
@@ -102,19 +102,19 @@ const CommonCommandTableEntry commonCommandTable[] = {
// These change the vertex shader so need flushing.
{ GE_CMD_REVERSENORMAL, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
- { GE_CMD_LIGHTINGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
- { GE_CMD_LIGHTENABLE0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
- { GE_CMD_LIGHTENABLE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
- { GE_CMD_LIGHTENABLE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
- { GE_CMD_LIGHTENABLE3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
+ { GE_CMD_LIGHTINGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE },
+ { GE_CMD_LIGHTENABLE0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
+ { GE_CMD_LIGHTENABLE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
+ { GE_CMD_LIGHTENABLE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
+ { GE_CMD_LIGHTENABLE3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
{ GE_CMD_LIGHTTYPE0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT0 },
{ GE_CMD_LIGHTTYPE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT1 },
{ GE_CMD_LIGHTTYPE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT2 },
{ GE_CMD_LIGHTTYPE3, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_LIGHT3 },
- { GE_CMD_MATERIALUPDATE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE }, // TODO: This should not need to dirty the fragment shader?
+ { GE_CMD_MATERIALUPDATE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
// These change both shaders so need flushing.
- { GE_CMD_LIGHTMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
+ { GE_CMD_LIGHTMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE },
{ GE_CMD_TEXFILTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS },
{ GE_CMD_TEXWRAP, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE },
@@ -425,6 +425,7 @@ GPUCommon::GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw) :
UpdateCmdInfo();
UpdateVsyncInterval(true);
+ ResetMatrices();
PPGeSetDrawContext(draw);
}
@@ -450,6 +451,29 @@ void GPUCommon::UpdateCmdInfo() {
cmdInfo_[GE_CMD_JUMP].func = &GPUCommon::Execute_Jump;
cmdInfo_[GE_CMD_CALL].func = &GPUCommon::Execute_Call;
}
+
+ // Reconfigure for light ubershader or not.
+ for (int i = 0; i < 4; i++) {
+ if (gstate_c.Supports(GPU_USE_LIGHT_UBERSHADER)) {
+ cmdInfo_[GE_CMD_LIGHTENABLE0 + i].RemoveDirty(DIRTY_VERTEXSHADER_STATE);
+ cmdInfo_[GE_CMD_LIGHTENABLE0 + i].AddDirty(DIRTY_LIGHT_CONTROL);
+ cmdInfo_[GE_CMD_LIGHTTYPE0 + i].RemoveDirty(DIRTY_VERTEXSHADER_STATE);
+ cmdInfo_[GE_CMD_LIGHTTYPE0 + i].AddDirty(DIRTY_LIGHT_CONTROL);
+ } else {
+ cmdInfo_[GE_CMD_LIGHTENABLE0 + i].RemoveDirty(DIRTY_LIGHT_CONTROL);
+ cmdInfo_[GE_CMD_LIGHTENABLE0 + i].AddDirty(DIRTY_VERTEXSHADER_STATE);
+ cmdInfo_[GE_CMD_LIGHTTYPE0 + i].RemoveDirty(DIRTY_LIGHT_CONTROL);
+ cmdInfo_[GE_CMD_LIGHTTYPE0 + i].AddDirty(DIRTY_VERTEXSHADER_STATE);
+ }
+ }
+
+ if (gstate_c.Supports(GPU_USE_LIGHT_UBERSHADER)) {
+ cmdInfo_[GE_CMD_MATERIALUPDATE].RemoveDirty(DIRTY_VERTEXSHADER_STATE);
+ cmdInfo_[GE_CMD_MATERIALUPDATE].AddDirty(DIRTY_LIGHT_CONTROL);
+ } else {
+ cmdInfo_[GE_CMD_MATERIALUPDATE].RemoveDirty(DIRTY_LIGHT_CONTROL);
+ cmdInfo_[GE_CMD_MATERIALUPDATE].AddDirty(DIRTY_VERTEXSHADER_STATE);
+ }
}
void GPUCommon::BeginHostFrame() {
@@ -708,6 +732,59 @@ int GPUCommon::GetStack(int index, u32 stackPtr) {
return currentList->stackptr;
}
+static void CopyMatrix24(u32_le *result, const float *mtx, u32 count, u32 cmdbits) {
+ for (u32 i = 0; i < count; ++i) {
+ result[i] = toFloat24(mtx[i]) | cmdbits;
+ }
+}
+
+bool GPUCommon::GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) {
+ switch (type) {
+ case GE_MTX_BONE0:
+ case GE_MTX_BONE1:
+ case GE_MTX_BONE2:
+ case GE_MTX_BONE3:
+ case GE_MTX_BONE4:
+ case GE_MTX_BONE5:
+ case GE_MTX_BONE6:
+ case GE_MTX_BONE7:
+ CopyMatrix24(result, gstate.boneMatrix + (type - GE_MTX_BONE0) * 12, 12, cmdbits);
+ break;
+ case GE_MTX_TEXGEN:
+ CopyMatrix24(result, gstate.tgenMatrix, 12, cmdbits);
+ break;
+ case GE_MTX_WORLD:
+ CopyMatrix24(result, gstate.worldMatrix, 12, cmdbits);
+ break;
+ case GE_MTX_VIEW:
+ CopyMatrix24(result, gstate.viewMatrix, 12, cmdbits);
+ break;
+ case GE_MTX_PROJECTION:
+ CopyMatrix24(result, gstate.projMatrix, 16, cmdbits);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+void GPUCommon::ResetMatrices() {
+ // This means we restored a context, so update the visible matrix data.
+ for (size_t i = 0; i < ARRAY_SIZE(gstate.boneMatrix); ++i)
+ matrixVisible.bone[i] = toFloat24(gstate.boneMatrix[i]);
+ for (size_t i = 0; i < ARRAY_SIZE(gstate.worldMatrix); ++i)
+ matrixVisible.world[i] = toFloat24(gstate.worldMatrix[i]);
+ for (size_t i = 0; i < ARRAY_SIZE(gstate.viewMatrix); ++i)
+ matrixVisible.view[i] = toFloat24(gstate.viewMatrix[i]);
+ for (size_t i = 0; i < ARRAY_SIZE(gstate.projMatrix); ++i)
+ matrixVisible.proj[i] = toFloat24(gstate.projMatrix[i]);
+ for (size_t i = 0; i < ARRAY_SIZE(gstate.tgenMatrix); ++i)
+ matrixVisible.tgen[i] = toFloat24(gstate.tgenMatrix[i]);
+
+ // Assume all the matrices changed, so dirty things related to them.
+ gstate_c.Dirty(DIRTY_WORLDMATRIX | DIRTY_VIEWMATRIX | DIRTY_PROJMATRIX | DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE | DIRTY_BONE_UNIFORMS);
+}
+
u32 GPUCommon::EnqueueList(u32 listpc, u32 stall, int subIntrBase, PSPPointer args, bool head) {
// TODO Check the stack values in missing arg and ajust the stack depth
@@ -1203,6 +1280,15 @@ void GPUCommon::ReapplyGfxState() {
// Let's just skip the transfer size stuff, it's just values.
}
+uint32_t GPUCommon::SetAddrTranslation(uint32_t value) {
+ std::swap(edramTranslation_, value);
+ return value;
+}
+
+uint32_t GPUCommon::GetAddrTranslation() {
+ return edramTranslation_;
+}
+
inline void GPUCommon::UpdateState(GPURunState state) {
gpuState = state;
if (state != GPUSTATE_RUNNING)
@@ -1267,7 +1353,8 @@ void GPUCommon::Execute_Iaddr(u32 op, u32 diff) {
}
void GPUCommon::Execute_Origin(u32 op, u32 diff) {
- gstate_c.offsetAddr = currentList->pc;
+ if (currentList)
+ gstate_c.offsetAddr = currentList->pc;
}
void GPUCommon::Execute_Jump(u32 op, u32 diff) {
@@ -1330,7 +1417,7 @@ void GPUCommon::DoExecuteCall(u32 target) {
// Check for the end
if ((Memory::ReadUnchecked_U32(target + 11 * 4) >> 24) == GE_CMD_BONEMATRIXDATA &&
(Memory::ReadUnchecked_U32(target + 12 * 4) >> 24) == GE_CMD_RET &&
- (gstate.boneMatrixNumber & 0x7F) <= 96 - 12) {
+ (gstate.boneMatrixNumber & 0x00FFFFFF) <= 96 - 12) {
// Yep, pretty sure this is a bone matrix call. Double check stall first.
if (target > currentList->stall || target + 12 * 4 < currentList->stall) {
FastLoadBoneMatrix(target);
@@ -1596,7 +1683,7 @@ void GPUCommon::Execute_VertexType(u32 op, u32 diff) {
if (diff & (GE_VTYPE_TC_MASK | GE_VTYPE_THROUGH_MASK)) {
gstate_c.Dirty(DIRTY_UVSCALEOFFSET);
if (diff & GE_VTYPE_THROUGH_MASK)
- gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_CULLRANGE);
+ gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE);
}
}
@@ -1623,7 +1710,7 @@ void GPUCommon::Execute_VertexTypeSkinning(u32 op, u32 diff) {
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE);
}
if (diff & GE_VTYPE_THROUGH_MASK)
- gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_CULLRANGE);
+ gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE);
}
void GPUCommon::CheckDepthUsage(VirtualFramebuffer *vfb) {
@@ -1864,7 +1951,7 @@ void GPUCommon::Execute_Prim(u32 op, u32 diff) {
(Memory::ReadUnchecked_U32(target + 11 * 4) >> 24) == GE_CMD_BONEMATRIXDATA &&
(Memory::ReadUnchecked_U32(target + 12 * 4) >> 24) == GE_CMD_RET &&
(target > currentList->stall || target + 12 * 4 < currentList->stall) &&
- (gstate.boneMatrixNumber & 0x7F) <= 96 - 12) {
+ (gstate.boneMatrixNumber & 0x00FFFFFF) <= 96 - 12) {
FastLoadBoneMatrix(target);
} else {
goto bail;
@@ -1942,7 +2029,8 @@ void GPUCommon::Execute_Bezier(u32 op, u32 diff) {
}
// Can't flush after setting gstate_c.submitType below since it'll be a mess - it must be done already.
- drawEngineCommon_->DispatchFlush();
+ if (flushOnParams_)
+ drawEngineCommon_->DispatchFlush();
Spline::BezierSurface surface;
surface.tess_u = gstate.getPatchDivisionU();
@@ -1956,8 +2044,8 @@ void GPUCommon::Execute_Bezier(u32 op, u32 diff) {
SetDrawType(DRAW_BEZIER, PatchPrimToPrim(surface.primType));
+ gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE);
if (drawEngineCommon_->CanUseHardwareTessellation(surface.primType)) {
- gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE);
gstate_c.submitType = SubmitType::HW_BEZIER;
if (gstate_c.spline_num_points_u != surface.num_points_u) {
gstate_c.Dirty(DIRTY_BEZIERSPLINE);
@@ -1971,7 +2059,7 @@ void GPUCommon::Execute_Bezier(u32 op, u32 diff) {
UpdateUVScaleOffset();
drawEngineCommon_->SubmitCurve(control_points, indices, surface, gstate.vertType, &bytesRead, "bezier");
- gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE);
+ gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE);
gstate_c.submitType = SubmitType::DRAW;
// After drawing, we advance pointers - see SubmitPrim which does the same.
@@ -2014,7 +2102,8 @@ void GPUCommon::Execute_Spline(u32 op, u32 diff) {
}
// Can't flush after setting gstate_c.submitType below since it'll be a mess - it must be done already.
- drawEngineCommon_->DispatchFlush();
+ if (flushOnParams_)
+ drawEngineCommon_->DispatchFlush();
Spline::SplineSurface surface;
surface.tess_u = gstate.getPatchDivisionU();
@@ -2030,8 +2119,8 @@ void GPUCommon::Execute_Spline(u32 op, u32 diff) {
SetDrawType(DRAW_SPLINE, PatchPrimToPrim(surface.primType));
+ gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE);
if (drawEngineCommon_->CanUseHardwareTessellation(surface.primType)) {
- gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE);
gstate_c.submitType = SubmitType::HW_SPLINE;
if (gstate_c.spline_num_points_u != surface.num_points_u) {
gstate_c.Dirty(DIRTY_BEZIERSPLINE);
@@ -2045,7 +2134,7 @@ void GPUCommon::Execute_Spline(u32 op, u32 diff) {
UpdateUVScaleOffset();
drawEngineCommon_->SubmitCurve(control_points, indices, surface, gstate.vertType, &bytesRead, "spline");
- gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE);
+ gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE);
gstate_c.submitType = SubmitType::DRAW;
// After drawing, we advance pointers - see SubmitPrim which does the same.
@@ -2099,6 +2188,11 @@ void GPUCommon::Execute_BlockTransferStart(u32 op, u32 diff) {
}
void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) {
+ if (!currentList) {
+ gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (op & 0xF);
+ return;
+ }
+
// This is almost always followed by GE_CMD_WORLDMATRIXDATA.
const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4);
u32 *dst = (u32 *)(gstate.worldMatrix + (op & 0xF));
@@ -2106,7 +2200,7 @@ void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) {
int i = 0;
// We must record the individual data commands while debugRecording_.
- bool fastLoad = !debugRecording_;
+ bool fastLoad = !debugRecording_ && end > 0;
// Stalling in the middle of a matrix would be stupid, I doubt this check is necessary.
if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) {
fastLoad = false;
@@ -2127,7 +2221,7 @@ void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) {
}
const int count = i;
- gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | ((op + count) & 0xF);
+ gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | ((op & 0xF) + count);
// Skip over the loaded data, it's done now.
UpdatePC(currentList->pc, currentList->pc + count * 4);
@@ -2136,7 +2230,7 @@ void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) {
void GPUCommon::Execute_WorldMtxData(u32 op, u32 diff) {
// Note: it's uncommon to get here now, see above.
- int num = gstate.worldmtxnum & 0xF;
+ int num = gstate.worldmtxnum & 0x00FFFFFF;
u32 newVal = op << 8;
if (num < 12 && newVal != ((const u32 *)gstate.worldMatrix)[num]) {
Flush();
@@ -2144,18 +2238,23 @@ void GPUCommon::Execute_WorldMtxData(u32 op, u32 diff) {
gstate_c.Dirty(DIRTY_WORLDMATRIX);
}
num++;
- gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0xF);
+ gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0x00FFFFFF);
gstate.worldmtxdata = GE_CMD_WORLDMATRIXDATA << 24;
}
void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) {
+ if (!currentList) {
+ gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (op & 0xF);
+ return;
+ }
+
// This is almost always followed by GE_CMD_VIEWMATRIXDATA.
const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4);
u32 *dst = (u32 *)(gstate.viewMatrix + (op & 0xF));
const int end = 12 - (op & 0xF);
int i = 0;
- bool fastLoad = !debugRecording_;
+ bool fastLoad = !debugRecording_ && end > 0;
if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) {
fastLoad = false;
}
@@ -2175,7 +2274,7 @@ void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) {
}
const int count = i;
- gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | ((op + count) & 0xF);
+ gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | ((op & 0xF) + count);
// Skip over the loaded data, it's done now.
UpdatePC(currentList->pc, currentList->pc + count * 4);
@@ -2184,7 +2283,7 @@ void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) {
void GPUCommon::Execute_ViewMtxData(u32 op, u32 diff) {
// Note: it's uncommon to get here now, see above.
- int num = gstate.viewmtxnum & 0xF;
+ int num = gstate.viewmtxnum & 0x00FFFFFF;
u32 newVal = op << 8;
if (num < 12 && newVal != ((const u32 *)gstate.viewMatrix)[num]) {
Flush();
@@ -2192,11 +2291,16 @@ void GPUCommon::Execute_ViewMtxData(u32 op, u32 diff) {
gstate_c.Dirty(DIRTY_VIEWMATRIX);
}
num++;
- gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0xF);
+ gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0x00FFFFFF);
gstate.viewmtxdata = GE_CMD_VIEWMATRIXDATA << 24;
}
void GPUCommon::Execute_ProjMtxNum(u32 op, u32 diff) {
+ if (!currentList) {
+ gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (op & 0xF);
+ return;
+ }
+
// This is almost always followed by GE_CMD_PROJMATRIXDATA.
const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4);
u32 *dst = (u32 *)(gstate.projMatrix + (op & 0xF));
@@ -2223,7 +2327,7 @@ void GPUCommon::Execute_ProjMtxNum(u32 op, u32 diff) {
}
const int count = i;
- gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | ((op + count) & 0x1F);
+ gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | ((op & 0xF) + count);
// Skip over the loaded data, it's done now.
UpdatePC(currentList->pc, currentList->pc + count * 4);
@@ -2232,27 +2336,32 @@ void GPUCommon::Execute_ProjMtxNum(u32 op, u32 diff) {
void GPUCommon::Execute_ProjMtxData(u32 op, u32 diff) {
// Note: it's uncommon to get here now, see above.
- int num = gstate.projmtxnum & 0x1F; // NOTE: Changed from 0xF to catch overflows
+ int num = gstate.projmtxnum & 0x00FFFFFF;
u32 newVal = op << 8;
- if (num < 0x10 && newVal != ((const u32 *)gstate.projMatrix)[num]) {
+ if (num < 16 && newVal != ((const u32 *)gstate.projMatrix)[num]) {
Flush();
((u32 *)gstate.projMatrix)[num] = newVal;
gstate_c.Dirty(DIRTY_PROJMATRIX);
}
num++;
if (num <= 16)
- gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (num & 0xF);
+ gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (num & 0x00FFFFFF);
gstate.projmtxdata = GE_CMD_PROJMATRIXDATA << 24;
}
void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) {
+ if (!currentList) {
+ gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (op & 0xF);
+ return;
+ }
+
// This is almost always followed by GE_CMD_TGENMATRIXDATA.
const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4);
u32 *dst = (u32 *)(gstate.tgenMatrix + (op & 0xF));
const int end = 12 - (op & 0xF);
int i = 0;
- bool fastLoad = !debugRecording_;
+ bool fastLoad = !debugRecording_ && end > 0;
if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) {
fastLoad = false;
}
@@ -2263,7 +2372,8 @@ void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) {
if (dst[i] != newVal) {
Flush();
dst[i] = newVal;
- gstate_c.Dirty(DIRTY_TEXMATRIX);
+ // We check the matrix to see if we need projection.
+ gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE);
}
if (++i >= end) {
break;
@@ -2272,7 +2382,7 @@ void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) {
}
const int count = i;
- gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | ((op + count) & 0xF);
+ gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | ((op & 0xF) + count);
// Skip over the loaded data, it's done now.
UpdatePC(currentList->pc, currentList->pc + count * 4);
@@ -2281,7 +2391,7 @@ void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) {
void GPUCommon::Execute_TgenMtxData(u32 op, u32 diff) {
// Note: it's uncommon to get here now, see above.
- int num = gstate.texmtxnum & 0xF;
+ int num = gstate.texmtxnum & 0x00FFFFFF;
u32 newVal = op << 8;
if (num < 12 && newVal != ((const u32 *)gstate.tgenMatrix)[num]) {
Flush();
@@ -2289,11 +2399,16 @@ void GPUCommon::Execute_TgenMtxData(u32 op, u32 diff) {
gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE); // We check the matrix to see if we need projection
}
num++;
- gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (num & 0xF);
+ gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (num & 0x00FFFFFF);
gstate.texmtxdata = GE_CMD_TGENMATRIXDATA << 24;
}
void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) {
+ if (!currentList) {
+ gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (op & 0x7F);
+ return;
+ }
+
// This is almost always followed by GE_CMD_BONEMATRIXDATA.
const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4);
u32 *dst = (u32 *)(gstate.boneMatrix + (op & 0x7F));
@@ -2339,7 +2454,7 @@ void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) {
}
const int count = i;
- gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | ((op + count) & 0x7F);
+ gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | ((op & 0x7F) + count);
// Skip over the loaded data, it's done now.
UpdatePC(currentList->pc, currentList->pc + count * 4);
@@ -2348,7 +2463,7 @@ void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) {
void GPUCommon::Execute_BoneMtxData(u32 op, u32 diff) {
// Note: it's uncommon to get here now, see above.
- int num = gstate.boneMatrixNumber & 0x7F;
+ int num = gstate.boneMatrixNumber & 0x00FFFFFF;
u32 newVal = op << 8;
if (num < 96 && newVal != ((const u32 *)gstate.boneMatrix)[num]) {
// Bone matrices should NOT flush when software skinning is enabled!
@@ -2361,7 +2476,7 @@ void GPUCommon::Execute_BoneMtxData(u32 op, u32 diff) {
((u32 *)gstate.boneMatrix)[num] = newVal;
}
num++;
- gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x7F);
+ gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF);
gstate.boneMatrixData = GE_CMD_BONEMATRIXDATA << 24;
}
@@ -2414,10 +2529,10 @@ void GPUCommon::Execute_ImmVertexAlphaPrim(u32 op, u32 diff) {
immPrim_ = (GEPrimitiveType)prim;
// Flags seem to only be respected from the first prim.
immFlags_ = op & 0x00FFF800;
+ immFirstSent_ = false;
} else if (prim == GE_PRIM_KEEP_PREVIOUS && immPrim_ != GE_PRIM_INVALID) {
static constexpr int flushPrimCount[] = { 1, 2, 0, 3, 0, 0, 2, 0 };
- // Instead of finding a proper point to flush, we just emit a full rectangle every time one
- // is finished.
+ // Instead of finding a proper point to flush, we just emit prims when we can.
if (immCount_ == flushPrimCount[immPrim_ & 7])
FlushImm();
} else {
@@ -2439,31 +2554,6 @@ void GPUCommon::FlushImm() {
}
UpdateUVScaleOffset();
- // Instead of plumbing through properly (we'd need to inject these pretransformed vertices in the middle
- // of SoftwareTransform(), which would take a lot of refactoring), we'll cheat and just turn these into
- // through vertices.
- // Since the only known use is Thrillville and it only uses it to clear, we just use color and pos.
- struct ImmVertex {
- float uv[2];
- uint32_t color;
- float xyz[3];
- };
- ImmVertex temp[MAX_IMMBUFFER_SIZE];
- uint32_t color1Used = 0;
- for (int i = 0; i < immCount_; i++) {
- // Since we're sending through, scale back up to w/h.
- temp[i].uv[0] = immBuffer_[i].u * gstate.getTextureWidth(0);
- temp[i].uv[1] = immBuffer_[i].v * gstate.getTextureHeight(0);
- temp[i].color = immBuffer_[i].color0_32;
- temp[i].xyz[0] = immBuffer_[i].pos[0];
- temp[i].xyz[1] = immBuffer_[i].pos[1];
- temp[i].xyz[2] = immBuffer_[i].pos[2];
- color1Used |= immBuffer_[i].color1_32;
- }
- int vtype = GE_VTYPE_TC_FLOAT | GE_VTYPE_POS_FLOAT | GE_VTYPE_COL_8888 | GE_VTYPE_THROUGH;
-
- // TODO: Handle fog and secondary color somehow?
-
bool antialias = (immFlags_ & GE_IMM_ANTIALIAS) != 0;
bool prevAntialias = gstate.isAntiAliasEnabled();
bool shading = (immFlags_ & GE_IMM_SHADING) != 0;
@@ -2473,40 +2563,42 @@ void GPUCommon::FlushImm() {
int cullMode = (immFlags_ & GE_IMM_CULLFACE) != 0 ? 1 : 0;
bool texturing = (immFlags_ & GE_IMM_TEXTURE) != 0;
bool prevTexturing = gstate.isTextureMapEnabled();
+ bool fog = (immFlags_ & GE_IMM_FOG) != 0;
+ bool prevFog = gstate.isFogEnabled();
bool dither = (immFlags_ & GE_IMM_DITHER) != 0;
bool prevDither = gstate.isDitherEnabled();
if ((immFlags_ & GE_IMM_CLIPMASK) != 0) {
WARN_LOG_REPORT_ONCE(geimmclipvalue, G3D, "Imm vertex used clip value, flags=%06x", immFlags_);
- } else if ((immFlags_ & GE_IMM_FOG) != 0) {
- WARN_LOG_REPORT_ONCE(geimmfog, G3D, "Imm vertex used fog, flags=%06x", immFlags_);
- } else if (color1Used != 0 && gstate.isUsingSecondaryColor()) {
- WARN_LOG_REPORT_ONCE(geimmcolor1, G3D, "Imm vertex used secondary color, flags=%06x", immFlags_);
}
- if (texturing != prevTexturing || cullEnable != prevCullEnable || dither != prevDither || prevShading != shading) {
+ bool changed = texturing != prevTexturing || cullEnable != prevCullEnable || dither != prevDither;
+ changed = changed || prevShading != shading || prevFog != fog;
+ if (changed) {
DispatchFlush();
gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)antialias;
gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)shading;
gstate.cullfaceEnable = (GE_CMD_CULLFACEENABLE << 24) | (int)cullEnable;
gstate.textureMapEnable = (GE_CMD_TEXTUREMAPENABLE << 24) | (int)texturing;
+ gstate.fogEnable = (GE_CMD_FOGENABLE << 24) | (int)fog;
gstate.ditherEnable = (GE_CMD_DITHERENABLE << 24) | (int)dither;
- gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
+ gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET | DIRTY_CULLRANGE);
}
- int bytesRead;
- uint32_t vertTypeID = GetVertTypeID(vtype, 0);
- drawEngineCommon_->DispatchSubmitImm(temp, nullptr, immPrim_, immCount_, vertTypeID, cullMode, &bytesRead);
- // TODO: In the future, make a special path for these.
- // drawEngineCommon_->DispatchSubmitImm(immBuffer_, immCount_);
+ drawEngineCommon_->DispatchSubmitImm(immPrim_, immBuffer_, immCount_, cullMode, immFirstSent_);
immCount_ = 0;
+ immFirstSent_ = true;
- gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)prevAntialias;
- gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)prevShading;
- gstate.cullfaceEnable = (GE_CMD_CULLFACEENABLE << 24) | (int)prevCullEnable;
- gstate.textureMapEnable = (GE_CMD_TEXTUREMAPENABLE << 24) | (int)prevTexturing;
- gstate.ditherEnable = (GE_CMD_DITHERENABLE << 24) | (int)prevDither;
- gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
+ if (changed) {
+ DispatchFlush();
+ gstate.antiAliasEnable = (GE_CMD_ANTIALIASENABLE << 24) | (int)prevAntialias;
+ gstate.shademodel = (GE_CMD_SHADEMODE << 24) | (int)prevShading;
+ gstate.cullfaceEnable = (GE_CMD_CULLFACEENABLE << 24) | (int)prevCullEnable;
+ gstate.textureMapEnable = (GE_CMD_TEXTUREMAPENABLE << 24) | (int)prevTexturing;
+ gstate.fogEnable = (GE_CMD_FOGENABLE << 24) | (int)prevFog;
+ gstate.ditherEnable = (GE_CMD_DITHERENABLE << 24) | (int)prevDither;
+ gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET | DIRTY_CULLRANGE);
+ }
}
void GPUCommon::ExecuteOp(u32 op, u32 diff) {
@@ -2623,7 +2715,7 @@ struct DisplayList_v2 {
};
void GPUCommon::DoState(PointerWrap &p) {
- auto s = p.Section("GPUCommon", 1, 4);
+ auto s = p.Section("GPUCommon", 1, 6);
if (!s)
return;
@@ -2695,6 +2787,13 @@ void GPUCommon::DoState(PointerWrap &p) {
Do(p, isbreak);
Do(p, drawCompleteTicks);
Do(p, busyTicks);
+
+ if (s >= 5) {
+ Do(p, matrixVisible.all);
+ }
+ if (s >= 6) {
+ Do(p, edramTranslation_);
+ }
}
void GPUCommon::InterruptStart(int listid) {
@@ -2947,10 +3046,10 @@ void GPUCommon::DoBlockTransfer(u32 skipDrawReason) {
cyclesExecuted += ((height * width * bpp) * 16) / 10;
}
-bool GPUCommon::PerformMemoryCopy(u32 dest, u32 src, int size) {
+bool GPUCommon::PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags) {
// Track stray copies of a framebuffer in RAM. MotoGP does this.
if (framebufferManager_->MayIntersectFramebuffer(src) || framebufferManager_->MayIntersectFramebuffer(dest)) {
- if (!framebufferManager_->NotifyFramebufferCopy(src, dest, size, false, gstate_c.skipDrawReason)) {
+ if (!framebufferManager_->NotifyFramebufferCopy(src, dest, size, flags, gstate_c.skipDrawReason)) {
// We use a little hack for PerformMemoryDownload/PerformMemoryUpload using a VRAM mirror.
// Since they're identical we don't need to copy.
if (!Memory::IsVRAMAddress(dest) || (dest ^ 0x00400000) != src) {
@@ -2972,7 +3071,8 @@ bool GPUCommon::PerformMemoryCopy(u32 dest, u32 src, int size) {
NotifyMemInfo(MemBlockFlags::WRITE, dest, size, tag.c_str(), tag.size());
}
InvalidateCache(dest, size, GPU_INVALIDATE_HINT);
- GPURecord::NotifyMemcpy(dest, src, size);
+ if (!(flags & GPUCopyFlag::DEBUG_NOTIFIED))
+ GPURecord::NotifyMemcpy(dest, src, size);
return false;
}
@@ -2980,7 +3080,7 @@ bool GPUCommon::PerformMemorySet(u32 dest, u8 v, int size) {
// This may indicate a memset, usually to 0, of a framebuffer.
if (framebufferManager_->MayIntersectFramebuffer(dest)) {
Memory::Memset(dest, v, size, "GPUMemset");
- if (!framebufferManager_->NotifyFramebufferCopy(dest, dest, size, true, gstate_c.skipDrawReason)) {
+ if (!framebufferManager_->NotifyFramebufferCopy(dest, dest, size, GPUCopyFlag::MEMSET, gstate_c.skipDrawReason)) {
InvalidateCache(dest, size, GPU_INVALIDATE_HINT);
}
return true;
@@ -2994,20 +3094,16 @@ bool GPUCommon::PerformMemorySet(u32 dest, u8 v, int size) {
}
bool GPUCommon::PerformMemoryDownload(u32 dest, int size) {
- // Cheat a bit to force a download of the framebuffer.
- // VRAM + 0x00400000 is simply a VRAM mirror.
if (Memory::IsVRAMAddress(dest)) {
- return PerformMemoryCopy(dest ^ 0x00400000, dest, size);
+ return PerformMemoryCopy(dest, dest, size, GPUCopyFlag::FORCE_DST_MEM);
}
return false;
}
bool GPUCommon::PerformMemoryUpload(u32 dest, int size) {
- // Cheat a bit to force an upload of the framebuffer.
- // VRAM + 0x00400000 is simply a VRAM mirror.
if (Memory::IsVRAMAddress(dest)) {
GPURecord::NotifyUpload(dest, size);
- return PerformMemoryCopy(dest, dest ^ 0x00400000, size);
+ return PerformMemoryCopy(dest, dest, size, GPUCopyFlag::FORCE_SRC_MEM | GPUCopyFlag::DEBUG_NOTIFIED);
}
return false;
}
@@ -3176,3 +3272,57 @@ size_t GPUCommon::FormatGPUStatsCommon(char *buffer, size_t size) {
vertexAverageCycles
);
}
+
+u32 GPUCommon::CheckGPUFeatures() const {
+ u32 features = 0;
+ if (draw_->GetDeviceCaps().logicOpSupported) {
+ features |= GPU_SUPPORTS_LOGIC_OP;
+ }
+ if (draw_->GetDeviceCaps().anisoSupported) {
+ features |= GPU_SUPPORTS_ANISOTROPY;
+ }
+ if (draw_->GetDeviceCaps().textureNPOTFullySupported) {
+ features |= GPU_SUPPORTS_TEXTURE_NPOT;
+ }
+ if (draw_->GetDeviceCaps().dualSourceBlend) {
+ if (!g_Config.bVendorBugChecksEnabled || !draw_->GetBugs().Has(Draw::Bugs::DUAL_SOURCE_BLENDING_BROKEN)) {
+ features |= GPU_SUPPORTS_DUALSOURCE_BLEND;
+ }
+ }
+ if (draw_->GetDeviceCaps().blendMinMaxSupported) {
+ features |= GPU_SUPPORTS_BLEND_MINMAX;
+ }
+
+ if (draw_->GetDeviceCaps().clipDistanceSupported) {
+ features |= GPU_SUPPORTS_CLIP_DISTANCE;
+ }
+
+ if (draw_->GetDeviceCaps().cullDistanceSupported) {
+ features |= GPU_SUPPORTS_CULL_DISTANCE;
+ }
+
+ if (draw_->GetDeviceCaps().textureDepthSupported) {
+ features |= GPU_SUPPORTS_DEPTH_TEXTURE;
+ }
+
+ bool canClipOrCull = draw_->GetDeviceCaps().clipDistanceSupported || draw_->GetDeviceCaps().cullDistanceSupported;
+ bool canDiscardVertex = draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL);
+ if (canClipOrCull || canDiscardVertex) {
+ // We'll dynamically use the parts that are supported, to reduce artifacts as much as possible.
+ features |= GPU_SUPPORTS_VS_RANGE_CULLING;
+ }
+
+ if (draw_->GetDeviceCaps().framebufferFetchSupported) {
+ features |= GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH;
+ }
+
+ if (draw_->GetDeviceCaps().fragmentShaderInt32Supported) {
+ features |= GPU_USE_LIGHT_UBERSHADER;
+ }
+
+ if (PSP_CoreParameter().compat.flags().ClearToRAM) {
+ features |= GPU_USE_CLEAR_RAM_HACK;
+ }
+
+ return features;
+}
diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h
index 3a5674127f..3c478e2cec 100644
--- a/GPU/GPUCommon.h
+++ b/GPU/GPUCommon.h
@@ -76,7 +76,7 @@ public:
Draw::DrawContext *GetDrawContext() override {
return draw_;
}
- virtual void CheckGPUFeatures() = 0;
+ virtual u32 CheckGPUFeatures() const;
void UpdateCmdInfo();
@@ -103,7 +103,7 @@ public:
void ExecuteOp(u32 op, u32 diff) override;
void PreExecuteOp(u32 op, u32 diff) override;
- bool InterpretList(DisplayList &list) override;
+ bool InterpretList(DisplayList &list);
void ProcessDLQueue();
u32 UpdateStall(int listid, u32 newstall) override;
u32 EnqueueList(u32 listpc, u32 stall, int subIntrBase, PSPPointer args, bool head) override;
@@ -111,16 +111,20 @@ public:
int ListSync(int listid, int mode) override;
u32 DrawSync(int mode) override;
int GetStack(int index, u32 stackPtr) override;
+ bool GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) override;
+ void ResetMatrices() override;
void DoState(PointerWrap &p) override;
bool BusyDrawing() override;
u32 Continue() override;
u32 Break(int mode) override;
void ReapplyGfxState() override;
+ uint32_t SetAddrTranslation(uint32_t value) override;
+ uint32_t GetAddrTranslation() override;
void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override;
void CopyDisplayToOutput(bool reallyDirty) override = 0;
void InitClear() override = 0;
- bool PerformMemoryCopy(u32 dest, u32 src, int size) override;
+ bool PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags = GPUCopyFlag::NONE) override;
bool PerformMemorySet(u32 dest, u8 v, int size) override;
bool PerformMemoryDownload(u32 dest, int size) override;
bool PerformMemoryUpload(u32 dest, int size) override;
@@ -265,14 +269,14 @@ protected:
void SetDrawType(DrawType type, GEPrimitiveType prim) {
if (type != lastDraw_) {
// We always flush when drawing splines/beziers so no need to do so here
- gstate_c.Dirty(DIRTY_UVSCALEOFFSET | DIRTY_VERTEXSHADER_STATE);
+ gstate_c.Dirty(DIRTY_UVSCALEOFFSET | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE);
lastDraw_ = type;
}
// Prim == RECTANGLES can cause CanUseHardwareTransform to flip, so we need to dirty.
// Also, culling may be affected so dirty the raster state.
if (IsTrianglePrim(prim) != IsTrianglePrim(lastPrim_)) {
Flush();
- gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE);
+ gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE);
lastPrim_ = prim;
}
}
@@ -314,6 +318,14 @@ protected:
struct CommandInfo {
uint64_t flags;
GPUCommon::CmdFunc func;
+
+ // Dirty flags are mashed into the regular flags by a left shift of 8.
+ void AddDirty(u64 dirty) {
+ flags |= dirty << 8;
+ }
+ void RemoveDirty(u64 dirty) {
+ flags &= ~(dirty << 8);
+ }
};
static CommandInfo cmdInfo_[256];
@@ -355,6 +367,24 @@ protected:
int immCount_ = 0;
GEPrimitiveType immPrim_ = GE_PRIM_INVALID;
uint32_t immFlags_ = 0;
+ bool immFirstSent_ = false;
+
+ uint32_t edramTranslation_ = 0x400;
+
+ // Whe matrix data overflows, the CPU visible values wrap and bleed between matrices.
+ // But this doesn't actually change the values used by rendering.
+ // The CPU visible values affect the GPU when list contexts are restored.
+ // Note: not maintained by all backends, here for save stating.
+ union {
+ struct {
+ u32 bone[12 * 8];
+ u32 world[12];
+ u32 view[12];
+ u32 proj[16];
+ u32 tgen[12];
+ };
+ u32 all[12 * 8 + 12 + 12 + 16 + 12];
+ } matrixVisible;
std::string reportingPrimaryInfo_;
std::string reportingFullInfo_;
diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h
index 832897f598..e7cf2f9af6 100644
--- a/GPU/GPUInterface.h
+++ b/GPU/GPUInterface.h
@@ -114,6 +114,16 @@ enum class StencilUpload {
};
ENUM_CLASS_BITOPS(StencilUpload);
+enum class GPUCopyFlag {
+ NONE = 0,
+ FORCE_SRC_MEM = 1,
+ FORCE_DST_MEM = 2,
+ // Note: implies src == dst and FORCE_SRC_MEM.
+ MEMSET = 4,
+ DEBUG_NOTIFIED = 8,
+};
+ENUM_CLASS_BITOPS(GPUCopyFlag);
+
// Used for debug
struct FramebufferInfo {
u32 fb_address;
@@ -198,6 +208,9 @@ public:
virtual u32 Continue() = 0;
virtual u32 Break(int mode) = 0;
virtual int GetStack(int index, u32 stackPtr) = 0;
+ virtual bool GetMatrix24(GEMatrixType type, u32_le *result, u32 cmdbits) = 0;
+ virtual void ResetMatrices() = 0;
+ virtual uint32_t SetAddrTranslation(uint32_t value) = 0;
virtual void InterruptStart(int listid) = 0;
virtual void InterruptEnd(int listid) = 0;
@@ -205,7 +218,6 @@ public:
virtual void PreExecuteOp(u32 op, u32 diff) = 0;
virtual void ExecuteOp(u32 op, u32 diff) = 0;
- virtual bool InterpretList(DisplayList& list) = 0;
// Framebuffer management
virtual void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) = 0;
@@ -220,7 +232,7 @@ public:
virtual void InvalidateCache(u32 addr, int size, GPUInvalidationType type) = 0;
virtual void NotifyVideoUpload(u32 addr, int size, int width, int format) = 0;
// Update either RAM from VRAM, or VRAM from RAM... or even VRAM from VRAM.
- virtual bool PerformMemoryCopy(u32 dest, u32 src, int size) = 0;
+ virtual bool PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags = GPUCopyFlag::NONE) = 0;
virtual bool PerformMemorySet(u32 dest, u8 v, int size) = 0;
virtual bool PerformMemoryDownload(u32 dest, int size) = 0;
virtual bool PerformMemoryUpload(u32 dest, int size) = 0;
diff --git a/GPU/GPUState.cpp b/GPU/GPUState.cpp
index cec0f0c5d1..374816fa76 100644
--- a/GPU/GPUState.cpp
+++ b/GPU/GPUState.cpp
@@ -24,6 +24,7 @@
#include "Core/System.h"
#include "Core/MemMap.h"
#include "GPU/ge_constants.h"
+#include "GPU/GPUInterface.h"
#include "GPU/GPUState.h"
#ifdef _M_SSE
@@ -85,11 +86,20 @@ static const CmdRange contextCmdRanges[] = {
// Skip: {0xFA, 0xFF},
};
-static u32_le *SaveMatrix(u32_le *cmds, const float *mtx, int sz, int numcmd, int datacmd) {
+static u32_le *SaveMatrix(u32_le *cmds, GEMatrixType type, int sz, int numcmd, int datacmd) {
+ if (!gpu)
+ return cmds;
+
*cmds++ = numcmd << 24;
- for (int i = 0; i < sz; ++i) {
- *cmds++ = (datacmd << 24) | toFloat24(mtx[i]);
+ // This saves the CPU-visible values, not the actual used ones, which may differ.
+ // Note that Restore overwrites both values.
+ if (type == GE_MTX_BONE0) {
+ for (int i = 0; i < 8; ++i)
+ gpu->GetMatrix24(GEMatrixType(GE_MTX_BONE0 + i), cmds + i * 12, datacmd << 24);
+ } else {
+ gpu->GetMatrix24(type, cmds, datacmd << 24);
}
+ cmds += sz;
return cmds;
}
@@ -130,6 +140,7 @@ void GPUgstate::Save(u32_le *ptr) {
u32_le *cmds = ptr + 17;
for (size_t i = 0; i < ARRAY_SIZE(contextCmdRanges); ++i) {
for (int n = contextCmdRanges[i].start; n <= contextCmdRanges[i].end; ++n) {
+ // We'll run ReapplyGfxState after this to process dirtying.
*cmds++ = cmdmem[n];
}
}
@@ -152,17 +163,17 @@ void GPUgstate::Save(u32_le *ptr) {
memcpy(matrices, projMatrix, sizeof(projMatrix)); matrices += sizeof(projMatrix);
memcpy(matrices, tgenMatrix, sizeof(tgenMatrix)); matrices += sizeof(tgenMatrix);
} else {
- cmds = SaveMatrix(cmds, boneMatrix, ARRAY_SIZE(boneMatrix), GE_CMD_BONEMATRIXNUMBER, GE_CMD_BONEMATRIXDATA);
- cmds = SaveMatrix(cmds, worldMatrix, ARRAY_SIZE(worldMatrix), GE_CMD_WORLDMATRIXNUMBER, GE_CMD_WORLDMATRIXDATA);
- cmds = SaveMatrix(cmds, viewMatrix, ARRAY_SIZE(viewMatrix), GE_CMD_VIEWMATRIXNUMBER, GE_CMD_VIEWMATRIXDATA);
- cmds = SaveMatrix(cmds, projMatrix, ARRAY_SIZE(projMatrix), GE_CMD_PROJMATRIXNUMBER, GE_CMD_PROJMATRIXDATA);
- cmds = SaveMatrix(cmds, tgenMatrix, ARRAY_SIZE(tgenMatrix), GE_CMD_TGENMATRIXNUMBER, GE_CMD_TGENMATRIXDATA);
+ cmds = SaveMatrix(cmds, GE_MTX_BONE0, ARRAY_SIZE(boneMatrix), GE_CMD_BONEMATRIXNUMBER, GE_CMD_BONEMATRIXDATA);
+ cmds = SaveMatrix(cmds, GE_MTX_WORLD, ARRAY_SIZE(worldMatrix), GE_CMD_WORLDMATRIXNUMBER, GE_CMD_WORLDMATRIXDATA);
+ cmds = SaveMatrix(cmds, GE_MTX_VIEW, ARRAY_SIZE(viewMatrix), GE_CMD_VIEWMATRIXNUMBER, GE_CMD_VIEWMATRIXDATA);
+ cmds = SaveMatrix(cmds, GE_MTX_PROJECTION, ARRAY_SIZE(projMatrix), GE_CMD_PROJMATRIXNUMBER, GE_CMD_PROJMATRIXDATA);
+ cmds = SaveMatrix(cmds, GE_MTX_TEXGEN, ARRAY_SIZE(tgenMatrix), GE_CMD_TGENMATRIXNUMBER, GE_CMD_TGENMATRIXDATA);
- *cmds++ = boneMatrixNumber;
- *cmds++ = worldmtxnum;
- *cmds++ = viewmtxnum;
- *cmds++ = projmtxnum;
- *cmds++ = texmtxnum;
+ *cmds++ = boneMatrixNumber & 0xFF00007F;
+ *cmds++ = worldmtxnum & 0xFF00000F;
+ *cmds++ = viewmtxnum & 0xFF00000F;
+ *cmds++ = projmtxnum & 0xFF00000F;
+ *cmds++ = texmtxnum & 0xFF00000F;
*cmds++ = GE_CMD_END << 24;
}
}
@@ -199,7 +210,7 @@ void GPUgstate::FastLoadBoneMatrix(u32 addr) {
#endif
num += 12;
- gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x7F);
+ gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF);
}
void GPUgstate::Restore(u32_le *ptr) {
@@ -238,12 +249,15 @@ void GPUgstate::Restore(u32_le *ptr) {
cmds = LoadMatrix(cmds, projMatrix, ARRAY_SIZE(projMatrix));
cmds = LoadMatrix(cmds, tgenMatrix, ARRAY_SIZE(tgenMatrix));
- boneMatrixNumber = *cmds++;
- worldmtxnum = *cmds++;
- viewmtxnum = *cmds++;
- projmtxnum = *cmds++;
- texmtxnum = *cmds++;
+ boneMatrixNumber = (*cmds++) & 0xFF00007F;
+ worldmtxnum = (*cmds++) & 0xFF00000F;
+ viewmtxnum = (*cmds++) & 0xFF00000F;
+ projmtxnum = (*cmds++) & 0xFF00000F;
+ texmtxnum = (*cmds++) & 0xFF00000F;
}
+
+ if (gpu)
+ gpu->ResetMatrices();
}
bool vertTypeIsSkinningEnabled(u32 vertType) {
diff --git a/GPU/GPUState.h b/GPU/GPUState.h
index cbfb58395d..fa9d4b575c 100644
--- a/GPU/GPUState.h
+++ b/GPU/GPUState.h
@@ -206,13 +206,14 @@ struct GPUgstate {
float boneMatrix[12 * 8]; // Eight 4x3 bone matrices.
// We ignore the high bits of the framebuffer in fbwidth - even 0x08000000 renders to vRAM.
- u32 getFrameBufRawAddress() const { return (fbptr & 0xFFFFFF); }
+ // The top bits of mirroring are also not respected, so we mask them away.
+ u32 getFrameBufRawAddress() const { return fbptr & 0x1FFFF0; }
// 0x44000000 is uncached VRAM.
u32 getFrameBufAddress() const { return 0x44000000 | getFrameBufRawAddress(); }
GEBufferFormat FrameBufFormat() const { return static_cast(framebufpixformat & 3); }
int FrameBufStride() const { return fbwidth&0x7FC; }
- u32 getDepthBufRawAddress() const { return (zbptr & 0xFFFFFF); }
- u32 getDepthBufAddress() const { return 0x44000000 | getDepthBufRawAddress(); }
+ u32 getDepthBufRawAddress() const { return zbptr & 0x1FFFF0; }
+ u32 getDepthBufAddress() const { return 0x44600000 | getDepthBufRawAddress(); }
int DepthBufStride() const { return zbwidth&0x7FC; }
// Pixel Pipeline
@@ -469,12 +470,13 @@ struct UVScale {
// Might want to move this mechanism into the backend later.
enum {
GPU_SUPPORTS_DUALSOURCE_BLEND = FLAG_BIT(0),
- // Free bit: 1
- GPU_SUPPORTS_GLSL_330 = FLAG_BIT(2),
+ GPU_USE_LIGHT_UBERSHADER = FLAG_BIT(1),
+ // Free bit: 2
GPU_SUPPORTS_VS_RANGE_CULLING = FLAG_BIT(3),
GPU_SUPPORTS_BLEND_MINMAX = FLAG_BIT(4),
GPU_SUPPORTS_LOGIC_OP = FLAG_BIT(5),
GPU_USE_DEPTH_RANGE_HACK = FLAG_BIT(6),
+ // Free bit: 7
GPU_SUPPORTS_ANISOTROPY = FLAG_BIT(8),
GPU_USE_CLEAR_RAM_HACK = FLAG_BIT(9),
GPU_SUPPORTS_INSTANCE_RENDERING = FLAG_BIT(10),
@@ -485,7 +487,8 @@ enum {
// Free bit: 15
GPU_SUPPORTS_DEPTH_TEXTURE = FLAG_BIT(16),
GPU_SUPPORTS_ACCURATE_DEPTH = FLAG_BIT(17),
- // Free bits: 18-19
+ GPU_SUPPORTS_GS_CULLING = FLAG_BIT(18), // Geometry shader
+ // Free bit: 19
GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH = FLAG_BIT(20),
GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT = FLAG_BIT(21),
GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT = FLAG_BIT(22),
diff --git a/GPU/Math3D.cpp b/GPU/Math3D.cpp
index 7da413829f..ecb959ec2c 100644
--- a/GPU/Math3D.cpp
+++ b/GPU/Math3D.cpp
@@ -121,7 +121,8 @@ __m128 SSENormalizeMultiplierSSE2(__m128 v)
#endif
__m128 SSENormalizeMultiplierSSE4(__m128 v)
{
- return _mm_rsqrt_ps(_mm_dp_ps(v, v, 0xFF));
+ // This is only used for Vec3f, so ignore the 4th component, might be garbage.
+ return _mm_rsqrt_ps(_mm_dp_ps(v, v, 0x77));
}
__m128 SSENormalizeMultiplier(bool useSSE4, __m128 v)
diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp
index 3438cb222d..9a008e6658 100644
--- a/GPU/Software/BinManager.cpp
+++ b/GPU/Software/BinManager.cpp
@@ -161,13 +161,13 @@ BinManager::~BinManager() {
}
}
-void BinManager::UpdateState(bool throughMode) {
+void BinManager::UpdateState() {
PROFILE_THIS_SCOPE("bin_state");
if (HasDirty(SoftDirty::PIXEL_ALL | SoftDirty::SAMPLER_ALL | SoftDirty::RAST_ALL)) {
if (states_.Full())
Flush("states");
stateIndex_ = (uint16_t)states_.Push(RasterizerState());
- ComputeRasterizerState(&states_[stateIndex_], throughMode);
+ ComputeRasterizerState(&states_[stateIndex_]);
states_[stateIndex_].samplerID.cached.clut = cluts_[clutIndex_].readable;
ClearDirty(SoftDirty::PIXEL_ALL | SoftDirty::SAMPLER_ALL | SoftDirty::RAST_ALL);
@@ -197,16 +197,16 @@ void BinManager::UpdateState(bool throughMode) {
Flush("tex");
// Okay, now update what's pending.
- constexpr uint32_t mirrorMask = 0x0FFFFFFF & ~0x00600000;
- const uint32_t bpp = state.pixelID.FBFormat() == GE_FORMAT_8888 ? 4 : 2;
- pendingWrites_[0].Expand(gstate.getFrameBufAddress() & mirrorMask, bpp, gstate.FrameBufStride(), scissorTL, scissorBR);
- if (state.pixelID.depthWrite)
- pendingWrites_[1].Expand(gstate.getDepthBufAddress() & mirrorMask, 2, gstate.DepthBufStride(), scissorTL, scissorBR);
+ MarkPendingWrites(state);
ClearDirty(SoftDirty::BINNER_RANGE);
} else if (pendingOverlap_) {
- if (HasTextureWrite(state))
+ if (HasTextureWrite(state)) {
Flush("tex");
+
+ // We need the pending writes set, which flushing cleared. Set them again.
+ MarkPendingWrites(state);
+ }
}
if (HasDirty(SoftDirty::BINNER_OVERLAP)) {
@@ -282,6 +282,17 @@ void BinManager::MarkPendingReads(const Rasterizer::RasterizerState &state) {
}
}
+void BinManager::MarkPendingWrites(const Rasterizer::RasterizerState &state) {
+ DrawingCoords scissorTL(gstate.getScissorX1(), gstate.getScissorY1());
+ DrawingCoords scissorBR(std::min(gstate.getScissorX2(), gstate.getRegionX2()), std::min(gstate.getScissorY2(), gstate.getRegionY2()));
+
+ constexpr uint32_t mirrorMask = 0x041FFFFF;
+ const uint32_t bpp = state.pixelID.FBFormat() == GE_FORMAT_8888 ? 4 : 2;
+ pendingWrites_[0].Expand(gstate.getFrameBufAddress() & mirrorMask, bpp, gstate.FrameBufStride(), scissorTL, scissorBR);
+ if (state.pixelID.depthWrite)
+ pendingWrites_[1].Expand(gstate.getDepthBufAddress() & mirrorMask, 2, gstate.DepthBufStride(), scissorTL, scissorBR);
+}
+
inline void BinDirtyRange::Expand(uint32_t newBase, uint32_t bpp, uint32_t stride, DrawingCoords &tl, DrawingCoords &br) {
const uint32_t w = br.x - tl.x + 1;
const uint32_t h = br.y - tl.y + 1;
@@ -326,7 +337,7 @@ void BinManager::AddTriangle(const VertexData &v0, const VertexData &v1, const V
if (d01.x * d02.y - d01.y * d02.x < 0)
return;
// If all points have identical coords, we'll have 0 weights and not skip properly, so skip here.
- if (d01.x == 0 && d01.y == 0 && d02.x == 0 && d02.y == 0)
+ if ((d01.x == 0 && d02.x == 0) || (d01.y == 0 && d02.y == 0))
return;
// Was it fully outside the scissor?
@@ -395,7 +406,7 @@ void BinManager::AddPoint(const VertexData &v0) {
Expand(range);
}
-void BinManager::Drain() {
+void BinManager::Drain(bool flushing) {
PROFILE_THIS_SCOPE("bin_drain");
// If the waitable has fully drained, we can update our binning decisions.
@@ -435,6 +446,7 @@ void BinManager::Drain() {
queue_.SkipNext();
}
} else {
+ int max = flushing ? QUEUED_PRIMS : QUEUED_PRIMS / 2;
while (!queue_.Empty()) {
const BinItem &item = queue_.PeekNext();
for (int i = 0; i < (int)taskRanges_.size(); ++i) {
@@ -442,17 +454,24 @@ void BinManager::Drain() {
if (range.Invalid())
continue;
- // This shouldn't often happen, but if it does, wait for space.
- if (taskQueues_[i].Full())
- waitable_->Wait();
+ if (taskQueues_[i].NearFull()) {
+ // This shouldn't often happen, but if it does, wait for space.
+ if (taskQueues_[i].Full())
+ waitable_->Wait();
+ // If we're not flushing and not near full, let's just continue later.
+ // Near full means we'd drain on next prim, so better to finish it now.
+ else if (!flushing && !queue_.NearFull())
+ max = 0;
+ }
BinItem &taskItem = taskQueues_[i].PeekPush();
taskItem = item;
taskItem.range = range;
taskQueues_[i].PushPeeked();
-
}
queue_.SkipNext();
+ if (--max <= 0)
+ break;
}
int threads = 0;
@@ -474,10 +493,13 @@ void BinManager::Drain() {
}
void BinManager::Flush(const char *reason) {
+ if (queueRange_.x1 == 0x7FFFFFFF)
+ return;
+
double st;
if (coreCollectDebugStats)
st = time_now_d();
- Drain();
+ Drain(true);
waitable_->Wait();
taskRanges_.clear();
tasksSplit_ = false;
@@ -516,7 +538,7 @@ bool BinManager::HasPendingWrite(uint32_t start, uint32_t stride, uint32_t w, ui
if (!Memory::IsVRAMAddress(start))
return false;
// Ignore mirrors for overlap detection.
- start &= 0x0FFFFFFF & ~0x00600000;
+ start &= 0x041FFFFF;
uint32_t size = stride * (h - 1) + w;
for (const auto &range : pendingWrites_) {
@@ -547,7 +569,7 @@ bool BinManager::HasPendingWrite(uint32_t start, uint32_t stride, uint32_t w, ui
bool BinManager::HasPendingRead(uint32_t start, uint32_t stride, uint32_t w, uint32_t h) {
if (Memory::IsVRAMAddress(start)) {
// Ignore VRAM mirrors.
- start &= 0x0FFFFFFF & ~0x00600000;
+ start &= 0x041FFFFF;
} else {
// Ignore only regular RAM mirrors.
start &= 0x3FFFFFFF;
diff --git a/GPU/Software/BinManager.h b/GPU/Software/BinManager.h
index 79146eae1d..d874f0d3c8 100644
--- a/GPU/Software/BinManager.h
+++ b/GPU/Software/BinManager.h
@@ -132,6 +132,10 @@ struct BinQueue {
return size_ == N - 1;
}
+ bool NearFull() const {
+ return size_ >= N - 2;
+ }
+
bool Empty() const {
return size_ == 0;
}
@@ -181,7 +185,7 @@ public:
BinManager();
~BinManager();
- void UpdateState(bool throughMode);
+ void UpdateState();
void UpdateClut(const void *src);
const Rasterizer::RasterizerState &State() {
@@ -195,7 +199,7 @@ public:
void AddLine(const VertexData &v0, const VertexData &v1);
void AddPoint(const VertexData &v0);
- void Drain();
+ void Drain(bool flushing = false);
void Flush(const char *reason);
bool HasPendingWrite(uint32_t start, uint32_t stride, uint32_t w, uint32_t h);
// Assumes you've also checked for a write (writes are partial so are automatically reads.)
@@ -228,8 +232,8 @@ protected:
static constexpr int QUEUED_STATES = 4096;
// These are 1KB each, so half an MB.
static constexpr int QUEUED_CLUTS = 512;
- // About 320 KB, but we have usually 16 or less of them, so 5 MB - 20 MB.
- static constexpr int QUEUED_PRIMS = 1024;
+ // About 360 KB, but we have usually 16 or less of them, so 5 MB - 22 MB.
+ static constexpr int QUEUED_PRIMS = 2048;
typedef BinQueue BinStateQueue;
typedef BinQueue BinClutQueue;
@@ -267,6 +271,7 @@ private:
int mostThreads_ = 0;
void MarkPendingReads(const Rasterizer::RasterizerState &state);
+ void MarkPendingWrites(const Rasterizer::RasterizerState &state);
bool HasTextureWrite(const Rasterizer::RasterizerState &state);
BinCoords Scissor(BinCoords range);
BinCoords Range(const VertexData &v0, const VertexData &v1, const VertexData &v2);
diff --git a/GPU/Software/Clipper.cpp b/GPU/Software/Clipper.cpp
index 166947ef14..2581cdf4c2 100644
--- a/GPU/Software/Clipper.cpp
+++ b/GPU/Software/Clipper.cpp
@@ -45,7 +45,7 @@ inline bool different_signs(float x, float y) {
return ((x <= 0 && y > 0) || (x > 0 && y <= 0));
}
-inline float clip_dotprod(const VertexData &vert, float A, float B, float C, float D) {
+inline float clip_dotprod(const ClipVertexData &vert, float A, float B, float C, float D) {
return (vert.clippos.x * A + vert.clippos.y * B + vert.clippos.z * C + vert.clippos.w * D);
}
@@ -131,8 +131,66 @@ static inline bool CheckOutsideZ(ClipCoords p, int &pos, int &neg) {
return false;
}
-void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner) {
+static void RotateUV(const VertexData &tl, const VertexData &br, VertexData &tr, VertexData &bl) {
+ const int x1 = tl.screenpos.x;
+ const int x2 = br.screenpos.x;
+ const int y1 = tl.screenpos.y;
+ const int y2 = br.screenpos.y;
+
+ if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2)) {
+ std::swap(bl.texturecoords, tr.texturecoords);
+ }
+}
+
+// This is used for rectangle texture projection, which is very uncommon.
+// To avoid complicating the common rectangle path, this just uses triangles.
+static void AddTriangleRect(const VertexData &v0, const VertexData &v1, BinManager &binner) {
+ VertexData buf[4];
+ buf[0] = v1;
+ buf[0].screenpos = ScreenCoords(v0.screenpos.x, v0.screenpos.y, v1.screenpos.z);
+ buf[0].texturecoords = v0.texturecoords;
+
+ buf[1] = v1;
+ buf[1].screenpos = ScreenCoords(v0.screenpos.x, v1.screenpos.y, v1.screenpos.z);
+ buf[1].texturecoords = Vec3Packed(v0.texturecoords.x, v1.texturecoords.y, v0.texturecoords.z);
+
+ buf[2] = v1;
+ buf[2].screenpos = ScreenCoords(v1.screenpos.x, v0.screenpos.y, v1.screenpos.z);
+ buf[2].texturecoords = Vec3Packed(v1.texturecoords.x, v0.texturecoords.y, v1.texturecoords.z);
+
+ buf[3] = v1;
+
+ VertexData *topleft = &buf[0];
+ VertexData *topright = &buf[1];
+ VertexData *bottomleft = &buf[2];
+ VertexData *bottomright = &buf[3];
+
+ // DrawTriangle always culls, so sort out the drawing order.
+ for (int i = 0; i < 4; ++i) {
+ if (buf[i].screenpos.x < topleft->screenpos.x && buf[i].screenpos.y < topleft->screenpos.y)
+ topleft = &buf[i];
+ if (buf[i].screenpos.x > topright->screenpos.x && buf[i].screenpos.y < topright->screenpos.y)
+ topright = &buf[i];
+ if (buf[i].screenpos.x < bottomleft->screenpos.x && buf[i].screenpos.y > bottomleft->screenpos.y)
+ bottomleft = &buf[i];
+ if (buf[i].screenpos.x > bottomright->screenpos.x && buf[i].screenpos.y > bottomright->screenpos.y)
+ bottomright = &buf[i];
+ }
+
+ RotateUV(v0, v1, *topright, *bottomleft);
+
+ binner.AddTriangle(*topleft, *topright, *bottomleft);
+ binner.AddTriangle(*bottomleft, *topright, *topleft);
+ binner.AddTriangle(*topright, *bottomright, *bottomleft);
+ binner.AddTriangle(*bottomleft, *bottomright, *topright);
+}
+
+void ProcessRect(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner) {
if (!binner.State().throughMode) {
+ // If any verts were outside range, throw the entire prim away.
+ if (v0.OutsideRange() || v1.OutsideRange())
+ return;
+
// We may discard the entire rect based on depth values.
int outsidePos = 0, outsideNeg = 0;
CheckOutsideZ(v0.clippos, outsidePos, outsideNeg);
@@ -145,48 +203,65 @@ void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner)
else if (outsidePos >= 2 || outsideNeg >= 2)
return;
- if (v0.fogdepth != v1.fogdepth) {
+ if (v0.v.fogdepth != v1.v.fogdepth) {
// Rectangles seem to always use nearest along X for fog depth, but reversed.
// TODO: Check exactness of middle.
- VertexData vhalf0 = v1;
- vhalf0.screenpos.x = v0.screenpos.x + (v1.screenpos.x - v0.screenpos.x) / 2;
+ VertexData vhalf0 = v1.v;
+ vhalf0.screenpos.x = v0.v.screenpos.x + (v1.v.screenpos.x - v0.v.screenpos.x) / 2;
- VertexData vhalf1 = v1;
- vhalf1.screenpos.x = v0.screenpos.x + (v1.screenpos.x - v0.screenpos.x) / 2;
- vhalf1.screenpos.y = v0.screenpos.y;
+ VertexData vhalf1 = v1.v;
+ vhalf1.screenpos.x = v0.v.screenpos.x + (v1.v.screenpos.x - v0.v.screenpos.x) / 2;
+ vhalf1.screenpos.y = v0.v.screenpos.y;
- VertexData vrev1 = v1;
- vrev1.fogdepth = v0.fogdepth;
+ VertexData vrev1 = v1.v;
+ vrev1.fogdepth = v0.v.fogdepth;
- binner.AddRect(v0, vhalf0);
- binner.AddRect(vhalf1, vrev1);
+ if (binner.State().textureProj) {
+ AddTriangleRect(v0.v, vhalf0, binner);
+ AddTriangleRect(vhalf1, vrev1, binner);
+ } else {
+ binner.AddRect(v0.v, vhalf0);
+ binner.AddRect(vhalf1, vrev1);
+ }
+ } else if (binner.State().textureProj) {
+ AddTriangleRect(v0.v, v1.v, binner);
} else {
- binner.AddRect(v0, v1);
+ binner.AddRect(v0.v, v1.v);
}
} else {
// through mode handling
- if (Rasterizer::RectangleFastPath(v0, v1, binner)) {
+ if (Rasterizer::RectangleFastPath(v0.v, v1.v, binner)) {
return;
} else if (gstate.isModeClear() && !gstate.isDitherEnabled()) {
- binner.AddClearRect(v0, v1);
+ binner.AddClearRect(v0.v, v1.v);
} else {
- binner.AddRect(v0, v1);
+ binner.AddRect(v0.v, v1.v);
}
}
}
-void ProcessPoint(const VertexData &v0, BinManager &binner) {
+void ProcessPoint(const ClipVertexData &v0, BinManager &binner) {
+ // If any verts were outside range, throw the entire prim away.
+ if (!binner.State().throughMode) {
+ if (v0.OutsideRange())
+ return;
+ }
+
// Points need no clipping. Will be bounds checked in the rasterizer (which seems backwards?)
- binner.AddPoint(v0);
+ binner.AddPoint(v0.v);
}
-void ProcessLine(const VertexData &v0, const VertexData &v1, BinManager &binner) {
+void ProcessLine(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner) {
if (binner.State().throughMode) {
// Actually, should clip this one too so we don't need to do bounds checks in the rasterizer.
- binner.AddLine(v0, v1);
+ binner.AddLine(v0.v, v1.v);
return;
}
+ // If any verts were outside range, throw the entire prim away.
+ if (v0.OutsideRange() || v1.OutsideRange())
+ return;
+
int outsidePos = 0, outsideNeg = 0;
CheckOutsideZ(v0.clippos, outsidePos, outsideNeg);
CheckOutsideZ(v1.clippos, outsidePos, outsideNeg);
@@ -202,26 +277,32 @@ void ProcessLine(const VertexData &v0, const VertexData &v1, BinManager &binner)
int mask1 = CalcClipMask(v1.clippos);
int mask = mask0 | mask1;
if ((mask & CLIP_NEG_Z_BIT) == 0) {
- binner.AddLine(v0, v1);
+ binner.AddLine(v0.v, v1.v);
return;
}
- VertexData ClippedVertices[2] = { v0, v1 };
- VertexData *Vertices[2] = { &ClippedVertices[0], &ClippedVertices[1] };
+ ClipVertexData ClippedVertices[2] = { v0, v1 };
+ ClipVertexData *Vertices[2] = { &ClippedVertices[0], &ClippedVertices[1] };
bool clipped = false;
CLIP_LINE(CLIP_NEG_Z_BIT, 0, 0, 1, 1);
- VertexData data[2] = { *Vertices[0], *Vertices[1] };
+ ClipVertexData data[2] = { *Vertices[0], *Vertices[1] };
if (clipped) {
- data[0].screenpos = TransformUnit::ClipToScreen(data[0].clippos);
- data[1].screenpos = TransformUnit::ClipToScreen(data[1].clippos);
+ data[0].v.screenpos = TransformUnit::ClipToScreen(data[0].clippos);
+ data[1].v.screenpos = TransformUnit::ClipToScreen(data[1].clippos);
+ data[0].v.clipw = data[0].clippos.w;
+ data[1].v.clipw = data[1].clippos.w;
}
- binner.AddLine(data[0], data[1]);
+ binner.AddLine(data[0].v, data[1].v);
}
-void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexData &v2, const VertexData &provoking, BinManager &binner) {
+void ProcessTriangle(const ClipVertexData &v0, const ClipVertexData &v1, const ClipVertexData &v2, const ClipVertexData &provoking, BinManager &binner) {
int mask = 0;
if (!binner.State().throughMode) {
+ // If any verts were outside range, throw the entire prim away.
+ if (v0.OutsideRange() || v1.OutsideRange() || v2.OutsideRange())
+ return;
+
mask |= CalcClipMask(v0.clippos);
mask |= CalcClipMask(v1.clippos);
mask |= CalcClipMask(v2.clippos);
@@ -244,20 +325,20 @@ void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexDat
if ((mask & CLIP_NEG_Z_BIT) == 0) {
if (gstate.getShadeMode() == GE_SHADE_FLAT) {
// So that the order of clipping doesn't matter...
- VertexData corrected2 = v2;
- corrected2.color0 = provoking.color0;
- corrected2.color1 = provoking.color1;
- binner.AddTriangle(v0, v1, corrected2);
+ VertexData corrected2 = v2.v;
+ corrected2.color0 = provoking.v.color0;
+ corrected2.color1 = provoking.v.color1;
+ binner.AddTriangle(v0.v, v1.v, corrected2);
} else {
- binner.AddTriangle(v0, v1, v2);
+ binner.AddTriangle(v0.v, v1.v, v2.v);
}
return;
}
enum { NUM_CLIPPED_VERTICES = 3, NUM_INDICES = NUM_CLIPPED_VERTICES + 3 };
- VertexData* Vertices[NUM_INDICES];
- VertexData ClippedVertices[NUM_INDICES];
+ ClipVertexData* Vertices[NUM_INDICES];
+ ClipVertexData ClippedVertices[NUM_INDICES];
for (int i = 0; i < NUM_INDICES; ++i)
Vertices[i] = &ClippedVertices[i];
@@ -301,22 +382,25 @@ void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexDat
for (int i = 0; i + 3 <= numIndices; i += 3) {
if (indices[i] != SKIP_FLAG) {
- VertexData &subv0 = *Vertices[indices[i + 0]];
- VertexData &subv1 = *Vertices[indices[i + 1]];
- VertexData &subv2 = *Vertices[indices[i + 2]];
+ ClipVertexData &subv0 = *Vertices[indices[i + 0]];
+ ClipVertexData &subv1 = *Vertices[indices[i + 1]];
+ ClipVertexData &subv2 = *Vertices[indices[i + 2]];
if (clipped) {
- subv0.screenpos = TransformUnit::ClipToScreen(subv0.clippos);
- subv1.screenpos = TransformUnit::ClipToScreen(subv1.clippos);
- subv2.screenpos = TransformUnit::ClipToScreen(subv2.clippos);
+ subv0.v.screenpos = TransformUnit::ClipToScreen(subv0.clippos);
+ subv1.v.screenpos = TransformUnit::ClipToScreen(subv1.clippos);
+ subv2.v.screenpos = TransformUnit::ClipToScreen(subv2.clippos);
+ subv0.v.clipw = subv0.clippos.w;
+ subv1.v.clipw = subv1.clippos.w;
+ subv2.v.clipw = subv2.clippos.w;
}
if (gstate.getShadeMode() == GE_SHADE_FLAT) {
// So that the order of clipping doesn't matter...
- subv2.color0 = provoking.color0;
- subv2.color1 = provoking.color1;
+ subv2.v.color0 = provoking.v.color0;
+ subv2.v.color1 = provoking.v.color1;
}
- binner.AddTriangle(subv0, subv1, subv2);
+ binner.AddTriangle(subv0.v, subv1.v, subv2.v);
}
}
}
diff --git a/GPU/Software/Clipper.h b/GPU/Software/Clipper.h
index c79fe7d730..587df83a42 100644
--- a/GPU/Software/Clipper.h
+++ b/GPU/Software/Clipper.h
@@ -26,9 +26,9 @@ class BinManager;
namespace Clipper {
-void ProcessPoint(const VertexData &v0, BinManager &binner);
-void ProcessLine(const VertexData &v0, const VertexData &v1, BinManager &binner);
-void ProcessTriangle(const VertexData &v0, const VertexData &v1, const VertexData &v2, const VertexData &provoking, BinManager &binner);
-void ProcessRect(const VertexData &v0, const VertexData &v1, BinManager &binner);
+void ProcessPoint(const ClipVertexData &v0, BinManager &binner);
+void ProcessLine(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner);
+void ProcessTriangle(const ClipVertexData &v0, const ClipVertexData &v1, const ClipVertexData &v2, const ClipVertexData &provoking, BinManager &binner);
+void ProcessRect(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner);
}
diff --git a/GPU/Software/DrawPixel.cpp b/GPU/Software/DrawPixel.cpp
index ee47c27b94..f47ea6656a 100644
--- a/GPU/Software/DrawPixel.cpp
+++ b/GPU/Software/DrawPixel.cpp
@@ -400,6 +400,206 @@ static inline u32 ApplyLogicOp(GELogicOp op, u32 old_color, u32 new_color) {
return new_color;
}
+static inline Vec3 GetSourceFactor(PixelBlendFactor factor, const Vec4 &source, const Vec4 &dst, uint32_t fix) {
+ switch (factor) {
+ case PixelBlendFactor::OTHERCOLOR:
+ return dst.rgb();
+
+ case PixelBlendFactor::INVOTHERCOLOR:
+ return Vec3::AssignToAll(255) - dst.rgb();
+
+ case PixelBlendFactor::SRCALPHA:
+#if defined(_M_SSE)
+ return Vec3(_mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3)));
+#else
+ return Vec3::AssignToAll(source.a());
+#endif
+
+ case PixelBlendFactor::INVSRCALPHA:
+#if defined(_M_SSE)
+ return Vec3(_mm_sub_epi32(_mm_set1_epi32(255), _mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3))));
+#else
+ return Vec3::AssignToAll(255 - source.a());
+#endif
+
+ case PixelBlendFactor::DSTALPHA:
+ return Vec3::AssignToAll(dst.a());
+
+ case PixelBlendFactor::INVDSTALPHA:
+ return Vec3::AssignToAll(255 - dst.a());
+
+ case PixelBlendFactor::DOUBLESRCALPHA:
+ return Vec3::AssignToAll(2 * source.a());
+
+ case PixelBlendFactor::DOUBLEINVSRCALPHA:
+ return Vec3::AssignToAll(255 - std::min(2 * source.a(), 255));
+
+ case PixelBlendFactor::DOUBLEDSTALPHA:
+ return Vec3::AssignToAll(2 * dst.a());
+
+ case PixelBlendFactor::DOUBLEINVDSTALPHA:
+ return Vec3::AssignToAll(255 - std::min(2 * dst.a(), 255));
+
+ case PixelBlendFactor::FIX:
+ default:
+ // All other dest factors (> 10) are treated as FIXA.
+ return Vec3::FromRGB(fix);
+
+ case PixelBlendFactor::ZERO:
+ return Vec3::AssignToAll(0);
+
+ case PixelBlendFactor::ONE:
+ return Vec3::AssignToAll(255);
+ }
+}
+
+static inline Vec3 GetDestFactor(PixelBlendFactor factor, const Vec4 &source, const Vec4 &dst, uint32_t fix) {
+ switch (factor) {
+ case PixelBlendFactor::OTHERCOLOR:
+ return source.rgb();
+
+ case PixelBlendFactor::INVOTHERCOLOR:
+ return Vec3::AssignToAll(255) - source.rgb();
+
+ case PixelBlendFactor::SRCALPHA:
+#if defined(_M_SSE)
+ return Vec3(_mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3)));
+#else
+ return Vec3::AssignToAll(source.a());
+#endif
+
+ case PixelBlendFactor::INVSRCALPHA:
+#if defined(_M_SSE)
+ return Vec3(_mm_sub_epi32(_mm_set1_epi32(255), _mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3))));
+#else
+ return Vec3::AssignToAll(255 - source.a());
+#endif
+
+ case PixelBlendFactor::DSTALPHA:
+ return Vec3::AssignToAll(dst.a());
+
+ case PixelBlendFactor::INVDSTALPHA:
+ return Vec3::AssignToAll(255 - dst.a());
+
+ case PixelBlendFactor::DOUBLESRCALPHA:
+ return Vec3::AssignToAll(2 * source.a());
+
+ case PixelBlendFactor::DOUBLEINVSRCALPHA:
+ return Vec3::AssignToAll(255 - std::min(2 * source.a(), 255));
+
+ case PixelBlendFactor::DOUBLEDSTALPHA:
+ return Vec3::AssignToAll(2 * dst.a());
+
+ case PixelBlendFactor::DOUBLEINVDSTALPHA:
+ return Vec3::AssignToAll(255 - std::min(2 * dst.a(), 255));
+
+ case PixelBlendFactor::FIX:
+ default:
+ // All other dest factors (> 10) are treated as FIXB.
+ return Vec3::FromRGB(fix);
+
+ case PixelBlendFactor::ZERO:
+ return Vec3::AssignToAll(0);
+
+ case PixelBlendFactor::ONE:
+ return Vec3::AssignToAll(255);
+ }
+}
+
+// Removed inline here - it was never chosen to be inlined by the compiler anyway, too complex.
+static Vec3 AlphaBlendingResult(const PixelFuncID &pixelID, const Vec4 &source, const Vec4 &dst) {
+ // Note: These factors cannot go below 0, but they can go above 255 when doubling.
+ Vec3 srcfactor = GetSourceFactor(pixelID.AlphaBlendSrc(), source, dst, pixelID.cached.alphaBlendSrc);
+ Vec3 dstfactor = GetDestFactor(pixelID.AlphaBlendDst(), source, dst, pixelID.cached.alphaBlendDst);
+
+ switch (pixelID.AlphaBlendEq()) {
+ case GE_BLENDMODE_MUL_AND_ADD:
+ {
+#if defined(_M_SSE)
+ // We switch to 16 bit to use mulhi, and we use 4 bits of decimal to make the 16 bit shift free.
+ const __m128i half = _mm_set1_epi16(1 << 3);
+
+ const __m128i srgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(source.ivec, source.ivec), 4), half);
+ const __m128i sf = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(srcfactor.ivec, srcfactor.ivec), 4), half);
+ const __m128i s = _mm_mulhi_epi16(srgb, sf);
+
+ const __m128i drgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dst.ivec, dst.ivec), 4), half);
+ const __m128i df = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dstfactor.ivec, dstfactor.ivec), 4), half);
+ const __m128i d = _mm_mulhi_epi16(drgb, df);
+
+ return Vec3(_mm_unpacklo_epi16(_mm_adds_epi16(s, d), _mm_setzero_si128()));
+#else
+ static constexpr Vec3 half = Vec3::AssignToAll(1);
+ Vec3 lhs = ((source.rgb() * 2 + half) * (srcfactor * 2 + half)) / 1024;
+ Vec3 rhs = ((dst.rgb() * 2 + half) * (dstfactor * 2 + half)) / 1024;
+ return lhs + rhs;
+#endif
+ }
+
+ case GE_BLENDMODE_MUL_AND_SUBTRACT:
+ {
+#if defined(_M_SSE)
+ const __m128i half = _mm_set1_epi16(1 << 3);
+
+ const __m128i srgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(source.ivec, source.ivec), 4), half);
+ const __m128i sf = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(srcfactor.ivec, srcfactor.ivec), 4), half);
+ const __m128i s = _mm_mulhi_epi16(srgb, sf);
+
+ const __m128i drgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dst.ivec, dst.ivec), 4), half);
+ const __m128i df = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dstfactor.ivec, dstfactor.ivec), 4), half);
+ const __m128i d = _mm_mulhi_epi16(drgb, df);
+
+ return Vec3(_mm_unpacklo_epi16(_mm_max_epi16(_mm_subs_epi16(s, d), _mm_setzero_si128()), _mm_setzero_si128()));
+#else
+ static constexpr Vec3 half = Vec3::AssignToAll(1);
+ Vec3 lhs = ((source.rgb() * 2 + half) * (srcfactor * 2 + half)) / 1024;
+ Vec3 rhs = ((dst.rgb() * 2 + half) * (dstfactor * 2 + half)) / 1024;
+ return lhs - rhs;
+#endif
+ }
+
+ case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE:
+ {
+#if defined(_M_SSE)
+ const __m128i half = _mm_set1_epi16(1 << 3);
+
+ const __m128i srgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(source.ivec, source.ivec), 4), half);
+ const __m128i sf = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(srcfactor.ivec, srcfactor.ivec), 4), half);
+ const __m128i s = _mm_mulhi_epi16(srgb, sf);
+
+ const __m128i drgb = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dst.ivec, dst.ivec), 4), half);
+ const __m128i df = _mm_add_epi16(_mm_slli_epi16(_mm_packs_epi32(dstfactor.ivec, dstfactor.ivec), 4), half);
+ const __m128i d = _mm_mulhi_epi16(drgb, df);
+
+ return Vec3(_mm_unpacklo_epi16(_mm_max_epi16(_mm_subs_epi16(d, s), _mm_setzero_si128()), _mm_setzero_si128()));
+#else
+ static constexpr Vec3 half = Vec3::AssignToAll(1);
+ Vec3 lhs = ((source.rgb() * 2 + half) * (srcfactor * 2 + half)) / 1024;
+ Vec3 rhs = ((dst.rgb() * 2 + half) * (dstfactor * 2 + half)) / 1024;
+ return rhs - lhs;
+#endif
+ }
+
+ case GE_BLENDMODE_MIN:
+ return Vec3(std::min(source.r(), dst.r()),
+ std::min(source.g(), dst.g()),
+ std::min(source.b(), dst.b()));
+
+ case GE_BLENDMODE_MAX:
+ return Vec3(std::max(source.r(), dst.r()),
+ std::max(source.g(), dst.g()),
+ std::max(source.b(), dst.b()));
+
+ case GE_BLENDMODE_ABSDIFF:
+ return Vec3(::abs(source.r() - dst.r()),
+ ::abs(source.g() - dst.g()),
+ ::abs(source.b() - dst.b()));
+
+ default:
+ return source.rgb();
+ }
+}
+
template
void SOFTRAST_CALL DrawSinglePixel(int x, int y, int z, int fog, Vec4IntArg color_in, const PixelFuncID &pixelID) {
Vec4 prim_color = Vec4(color_in).Clamp(0, 255);
@@ -673,11 +873,13 @@ void ComputePixelBlendState(PixelBlendState &state, const PixelFuncID &id) {
state.srcColorAsFactor = true;
break;
+ case PixelBlendFactor::ZERO:
+ state.readsDstPixel = state.dstColorAsFactor || state.usesDstAlpha;
+ break;
+
default:
break;
}
-
- state.dstColorAsFactor = state.dstColorAsFactor || state.usesDstAlpha;
}
}
diff --git a/GPU/Software/DrawPixel.h b/GPU/Software/DrawPixel.h
index f813bf61c1..65f6fa1ec5 100644
--- a/GPU/Software/DrawPixel.h
+++ b/GPU/Software/DrawPixel.h
@@ -51,6 +51,7 @@ struct PixelBlendState {
bool dstFactorIsInverse = false;
bool srcColorAsFactor = false;
bool dstColorAsFactor = false;
+ bool readsDstPixel = true;
};
void ComputePixelBlendState(PixelBlendState &state, const PixelFuncID &id);
diff --git a/GPU/Software/DrawPixelX86.cpp b/GPU/Software/DrawPixelX86.cpp
index dc53be1c62..53f9c94279 100644
--- a/GPU/Software/DrawPixelX86.cpp
+++ b/GPU/Software/DrawPixelX86.cpp
@@ -1047,7 +1047,13 @@ bool PixelJitCache::Jit_AlphaBlend(const PixelFuncID &id) {
// Step 1: Load and expand dest color.
X64Reg dstReg = regCache_.Alloc(RegCache::VEC_TEMP0);
- if (id.FBFormat() == GE_FORMAT_8888) {
+ if (!blendState.readsDstPixel) {
+ // Let's load colorOff just for registers to be consistent.
+ X64Reg colorOff = GetColorOff(id);
+ regCache_.Unlock(colorOff, RegCache::GEN_COLOR_OFF);
+
+ PXOR(dstReg, R(dstReg));
+ } else if (id.FBFormat() == GE_FORMAT_8888) {
X64Reg colorOff = GetColorOff(id);
Describe("AlphaBlend");
MOVD_xmm(dstReg, MatR(colorOff));
@@ -1073,7 +1079,6 @@ bool PixelJitCache::Jit_AlphaBlend(const PixelFuncID &id) {
case GE_FORMAT_4444:
success = success && Jit_ConvertFrom4444(id, dstGenReg, temp1Reg, temp2Reg, blendState.usesDstAlpha);
-
break;
case GE_FORMAT_8888:
@@ -1115,7 +1120,7 @@ bool PixelJitCache::Jit_AlphaBlend(const PixelFuncID &id) {
// We also need to add a half bit later, so this gives us space.
if (multiplySrc || blendState.srcColorAsFactor)
PSLLW(argColorReg, 4);
- if (multiplyDst || blendState.dstColorAsFactor)
+ if (multiplyDst || blendState.dstColorAsFactor || blendState.usesDstAlpha)
PSLLW(dstReg, 4);
// Okay, now grab our factors. Don't bother if they're known values.
@@ -1154,7 +1159,7 @@ bool PixelJitCache::Jit_AlphaBlend(const PixelFuncID &id) {
if (id.AlphaBlendEq() == GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE)
PXOR(dstReg, R(dstReg));
} else if (id.AlphaBlendDst() == PixelBlendFactor::ONE) {
- if (blendState.dstColorAsFactor)
+ if (blendState.dstColorAsFactor || blendState.usesDstAlpha)
PSRLW(dstReg, 4);
}
@@ -1219,7 +1224,6 @@ bool PixelJitCache::Jit_AlphaBlend(const PixelFuncID &id) {
return success;
}
-
bool PixelJitCache::Jit_BlendFactor(const PixelFuncID &id, RegCache::Reg factorReg, RegCache::Reg dstReg, PixelBlendFactor factor) {
X64Reg idReg = INVALID_REG;
X64Reg tempReg = INVALID_REG;
@@ -1733,6 +1737,7 @@ bool PixelJitCache::Jit_ApplyLogicOp(const PixelFuncID &id, RegCache::Reg colorR
}
std::vector finishes;
+ finishes.reserve(11);
FixupBranch skipTable = J(true);
const u8 *tableValues[16]{};
diff --git a/GPU/Software/FuncId.cpp b/GPU/Software/FuncId.cpp
index 501b5aaf27..e1520b8d72 100644
--- a/GPU/Software/FuncId.cpp
+++ b/GPU/Software/FuncId.cpp
@@ -48,15 +48,15 @@ static inline PixelBlendFactor OptimizeAlphaFactor(uint32_t color) {
return PixelBlendFactor::FIX;
}
-void ComputePixelFuncID(PixelFuncID *id, bool throughMode) {
+void ComputePixelFuncID(PixelFuncID *id) {
id->fullKey = 0;
// TODO: Could this be minz > 0x0000 || maxz < 0xFFFF? Maybe unsafe, depending on verts...
- id->applyDepthRange = !throughMode;
+ id->applyDepthRange = !gstate.isModeThrough();
// Dither happens even in clear mode.
id->dithering = gstate.isDitherEnabled();
id->fbFormat = gstate.FrameBufFormat();
- id->useStandardStride = gstate.FrameBufStride() == 512 && gstate.DepthBufStride() == 512;
+ id->useStandardStride = gstate.FrameBufStride() == 512;
id->applyColorWriteMask = gstate.getColorMask() != 0;
id->clearMode = gstate.isModeClear();
@@ -168,8 +168,16 @@ void ComputePixelFuncID(PixelFuncID *id, bool throughMode) {
id->alphaBlendDst = (uint8_t)OptimizeAlphaFactor(gstate.getFixB());
}
+ if (id->colorTest && gstate.getColorTestFunction() == GE_COMP_NOTEQUAL && gstate.getColorTestRef() == 0 && gstate.getColorTestMask() == 0xFFFFFF) {
+ if (!id->depthWrite && !id->stencilTest && id->alphaBlend && id->AlphaBlendEq() == GE_BLENDMODE_MUL_AND_ADD) {
+ // Might be a pointless color test (seen in Ridge Racer, for example.)
+ if (id->AlphaBlendDst() == PixelBlendFactor::ONE)
+ id->colorTest = false;
+ }
+ }
+
id->applyLogicOp = gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY;
- id->applyFog = gstate.isFogEnabled() && !throughMode;
+ id->applyFog = gstate.isFogEnabled() && !gstate.isModeThrough();
id->earlyZChecks = id->DepthTestFunc() != GE_COMP_ALWAYS;
if (id->stencilTest && id->earlyZChecks) {
@@ -179,6 +187,9 @@ void ComputePixelFuncID(PixelFuncID *id, bool throughMode) {
}
}
+ if (id->useStandardStride && (id->depthTestFunc != GE_COMP_ALWAYS || id->depthWrite))
+ id->useStandardStride = gstate.DepthBufStride() == 512;
+
// Cache some values for later convenience.
if (id->dithering) {
for (int y = 0; y < 4; ++y) {
diff --git a/GPU/Software/FuncId.h b/GPU/Software/FuncId.h
index 46307fc55c..d9d8e51573 100644
--- a/GPU/Software/FuncId.h
+++ b/GPU/Software/FuncId.h
@@ -244,7 +244,7 @@ struct hash {
};
-void ComputePixelFuncID(PixelFuncID *id, bool throughMode);
+void ComputePixelFuncID(PixelFuncID *id);
std::string DescribePixelFuncID(const PixelFuncID &id);
void ComputeSamplerID(SamplerID *id);
diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp
index 1f4039d05a..d59c1fb0cf 100644
--- a/GPU/Software/Rasterizer.cpp
+++ b/GPU/Software/Rasterizer.cpp
@@ -93,8 +93,8 @@ static inline Vec4 Interpolate(const float &c0, const float &c1, const fl
return Interpolate(c0, c1, c2, w0.Cast(), w1.Cast(), w2.Cast(), wsum_recip);
}
-void ComputeRasterizerState(RasterizerState *state, bool throughMode) {
- ComputePixelFuncID(&state->pixelID, throughMode);
+void ComputeRasterizerState(RasterizerState *state) {
+ ComputePixelFuncID(&state->pixelID);
state->drawPixel = Rasterizer::GetSingleFunc(state->pixelID);
state->enableTextures = gstate.isTextureMapEnabled() && !state->pixelID.clearMode;
@@ -129,10 +129,17 @@ void ComputeRasterizerState(RasterizerState *state, bool throughMode) {
state->mipFilt = gstate.isMipmapFilteringEnabled();
state->minFilt = gstate.isMinifyFilteringEnabled();
state->magFilt = gstate.isMagnifyFilteringEnabled();
+ state->textureProj = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX;
+ if (state->textureProj) {
+ // We may be able to optimize this off. This is actually kinda common.
+ if (gstate.tgenMatrix[2] == 0.0f && gstate.tgenMatrix[5] == 0.0f && gstate.tgenMatrix[8] == 0.0f && gstate.tgenMatrix[11] == 1.0f) {
+ state->textureProj = false;
+ }
+ }
}
state->shadeGouraud = gstate.getShadeMode() == GE_SHADE_GOURAUD;
- state->throughMode = throughMode;
+ state->throughMode = gstate.isModeThrough();
state->antialiasLines = gstate.isAntiAliasEnabled();
#if defined(SOFTGPU_MEMORY_TAGGING_DETAILED) || defined(SOFTGPU_MEMORY_TAGGING_BASIC)
@@ -143,6 +150,68 @@ void ComputeRasterizerState(RasterizerState *state, bool throughMode) {
#endif
}
+RasterizerState OptimizeFlatRasterizerState(RasterizerState state, const VertexData &v1) {
+ uint8_t alpha = v1.color0 >> 24;
+
+ bool changedPixelID = false;
+ bool changedSamplerID = false;
+ if (!state.pixelID.clearMode) {
+ auto &pixelID = state.pixelID;
+ auto &cached = pixelID.cached;
+
+ bool useTextureAlpha = state.enableTextures && state.samplerID.useTextureAlpha;
+ if (pixelID.alphaBlend && pixelID.AlphaBlendSrc() == PixelBlendFactor::SRCALPHA && !useTextureAlpha) {
+ // Okay, we may be able to convert this to a fixed value.
+ if (alpha == 0) {
+ pixelID.alphaBlendSrc = (uint8_t)PixelBlendFactor::ZERO;
+ changedPixelID = true;
+ } else if (alpha == 0xFF) {
+ pixelID.alphaBlendSrc = (uint8_t)PixelBlendFactor::ONE;
+ changedPixelID = true;
+ }
+ }
+ if (pixelID.alphaBlend && pixelID.AlphaBlendDst() == PixelBlendFactor::INVSRCALPHA && !useTextureAlpha) {
+ if (alpha == 0) {
+ pixelID.alphaBlendDst = (uint8_t)PixelBlendFactor::ONE;
+ changedPixelID = true;
+ } else if (alpha == 0xFF) {
+ pixelID.alphaBlendDst = (uint8_t)PixelBlendFactor::ZERO;
+ changedPixelID = true;
+ }
+ }
+ if (pixelID.alphaBlend && pixelID.AlphaBlendSrc() == PixelBlendFactor::ONE && pixelID.AlphaBlendDst() == PixelBlendFactor::ZERO) {
+ if (pixelID.AlphaBlendEq() == GE_BLENDMODE_MUL_AND_ADD) {
+ pixelID.alphaBlend = false;
+ changedPixelID = true;
+ }
+ }
+ }
+ if (state.enableTextures) {
+ if (v1.color0 == 0xFFFFFFFF) {
+ // Modulate is common, sometimes even with a fixed color. Replace is cheaper.
+ if (state.samplerID.TexFunc() == GE_TEXFUNC_MODULATE) {
+ state.samplerID.texFunc = (uint8_t)GE_TEXFUNC_REPLACE;
+ changedSamplerID = true;
+ }
+ }
+ }
+
+ if (changedPixelID)
+ state.drawPixel = Rasterizer::GetSingleFunc(state.pixelID);
+ if (changedSamplerID) {
+ state.linear = Sampler::GetLinearFunc(state.samplerID);
+ state.nearest = Sampler::GetNearestFunc(state.samplerID);
+
+ // Since the definitions are the same, just force this setting using the func pointer.
+ if (g_Config.iTexFiltering == TEX_FILTER_FORCE_LINEAR)
+ state.nearest = state.linear;
+ else if (g_Config.iTexFiltering == TEX_FILTER_FORCE_NEAREST)
+ state.linear = state.nearest;
+ }
+
+ return state;
+}
+
static inline u8 ClampFogDepth(float fogdepth) {
union FloatBits {
float f;
@@ -162,12 +231,9 @@ static inline u8 ClampFogDepth(float fogdepth) {
}
static inline void GetTextureCoordinates(const VertexData& v0, const VertexData& v1, const float p, float &s, float &t) {
- // All UV gen modes, by the time they get here, behave the same.
-
- // TODO: What happens if vertex has no texture coordinates?
// Note that for environment mapping, texture coordinates have been calculated during lighting
- float q0 = 1.f / v0.clippos.w;
- float q1 = 1.f / v1.clippos.w;
+ float q0 = 1.f / v0.clipw;
+ float q1 = 1.f / v1.clipw;
float wq0 = p * q0;
float wq1 = (1.0f - p) * q1;
@@ -176,14 +242,26 @@ static inline void GetTextureCoordinates(const VertexData& v0, const VertexData&
t = (v0.texturecoords.t() * wq0 + v1.texturecoords.t() * wq1) * q_recip;
}
-static inline void GetTextureCoordinates(const VertexData &v0, const VertexData &v1, const VertexData &v2, const Vec4 &w0, const Vec4 &w1, const Vec4 &w2, const Vec4 &wsum_recip, Vec4 &s, Vec4 &t) {
- // All UV gen modes, by the time they get here, behave the same.
+static inline void GetTextureCoordinatesProj(const VertexData& v0, const VertexData& v1, const float p, float &s, float &t) {
+ // This is for texture matrix projection.
+ float q0 = 1.f / v0.clipw;
+ float q1 = 1.f / v1.clipw;
+ float wq0 = p * q0;
+ float wq1 = (1.0f - p) * q1;
- // TODO: What happens if vertex has no texture coordinates?
+ float q_recip = 1.0f / (wq0 + wq1);
+ float q = (v0.texturecoords.q() * wq0 + v1.texturecoords.q() * wq1) * q_recip;
+ q_recip *= 1.0f / q;
+
+ s = (v0.texturecoords.s() * wq0 + v1.texturecoords.s() * wq1) * q_recip;
+ t = (v0.texturecoords.t() * wq0 + v1.texturecoords.t() * wq1) * q_recip;
+}
+
+static inline void GetTextureCoordinates(const VertexData &v0, const VertexData &v1, const VertexData &v2, const Vec4 &w0, const Vec4 &w1, const Vec4 &w2, const Vec4 &wsum_recip, Vec4 &s, Vec4 &t) {
// Note that for environment mapping, texture coordinates have been calculated during lighting.
- float q0 = 1.f / v0.clippos.w;
- float q1 = 1.f / v1.clippos.w;
- float q2 = 1.f / v2.clippos.w;
+ float q0 = 1.f / v0.clipw;
+ float q1 = 1.f / v1.clipw;
+ float q2 = 1.f / v2.clipw;
Vec4 wq0 = w0.Cast() * q0;
Vec4 wq1 = w1.Cast() * q1;
Vec4 wq2 = w2.Cast() * q2;
@@ -193,6 +271,23 @@ static inline void GetTextureCoordinates(const VertexData &v0, const VertexData
t = Interpolate(v0.texturecoords.t(), v1.texturecoords.t(), v2.texturecoords.t(), wq0, wq1, wq2, q_recip);
}
+static inline void GetTextureCoordinatesProj(const VertexData &v0, const VertexData &v1, const VertexData &v2, const Vec4 &w0, const Vec4 &w1, const Vec4 &w2, const Vec4 &wsum_recip, Vec4 &s, Vec4 &t) {
+ // This is for texture matrix projection.
+ float q0 = 1.f / v0.clipw;
+ float q1 = 1.f / v1.clipw;
+ float q2 = 1.f / v2.clipw;
+ Vec4 wq0 = w0.Cast() * q0;
+ Vec4 wq1 = w1.Cast() * q1;
+ Vec4 wq2 = w2.Cast() * q2;
+
+ Vec4 q_recip = (wq0 + wq1 + wq2).Reciprocal();
+ Vec4 q = Interpolate(v0.texturecoords.q(), v1.texturecoords.q(), v2.texturecoords.q(), wq0, wq1, wq2, q_recip);
+ q_recip = q_recip * q.Reciprocal();
+
+ s = Interpolate(v0.texturecoords.s(), v1.texturecoords.s(), v2.texturecoords.s(), wq0, wq1, wq2, q_recip);
+ t = Interpolate(v0.texturecoords.t(), v1.texturecoords.t(), v2.texturecoords.t(), wq0, wq1, wq2, q_recip);
+}
+
static inline void SetPixelDepth(int x, int y, int stride, u16 value) {
depthbuf.Set16(x, y, stride, value);
}
@@ -208,206 +303,6 @@ static inline bool IsRightSideOrFlatBottomLine(const Vec2& vertex, const Ve
}
}
-static inline Vec3 GetSourceFactor(PixelBlendFactor factor, const Vec4 &source, const Vec4 &dst, uint32_t fix) {
- switch (factor) {
- case PixelBlendFactor::OTHERCOLOR:
- return dst.rgb();
-
- case PixelBlendFactor::INVOTHERCOLOR:
- return Vec3::AssignToAll(255) - dst.rgb();
-
- case PixelBlendFactor::SRCALPHA:
-#if defined(_M_SSE)
- return Vec3(_mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3)));
-#else
- return Vec3::AssignToAll(source.a());
-#endif
-
- case PixelBlendFactor::INVSRCALPHA:
-#if defined(_M_SSE)
- return Vec3(_mm_sub_epi32(_mm_set1_epi32(255), _mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3))));
-#else
- return Vec3::AssignToAll(255 - source.a());
-#endif
-
- case PixelBlendFactor::DSTALPHA:
- return Vec3::AssignToAll(dst.a());
-
- case PixelBlendFactor::INVDSTALPHA:
- return Vec3::AssignToAll(255 - dst.a());
-
- case PixelBlendFactor::DOUBLESRCALPHA:
- return Vec3::AssignToAll(2 * source.a());
-
- case PixelBlendFactor::DOUBLEINVSRCALPHA:
- return Vec3::AssignToAll(255 - std::min(2 * source.a(), 255));
-
- case PixelBlendFactor::DOUBLEDSTALPHA:
- return Vec3::AssignToAll(2 * dst.a());
-
- case PixelBlendFactor::DOUBLEINVDSTALPHA:
- return Vec3::AssignToAll(255 - std::min(2 * dst.a(), 255));
-
- case PixelBlendFactor::FIX:
- default:
- // All other dest factors (> 10) are treated as FIXA.
- return Vec3::FromRGB(fix);
-
- case PixelBlendFactor::ZERO:
- return Vec3::AssignToAll(0);
-
- case PixelBlendFactor::ONE:
- return Vec3::AssignToAll(255);
- }
-}
-
-static inline Vec3 GetDestFactor(PixelBlendFactor factor, const Vec4 &source, const Vec4 &dst, uint32_t fix) {
- switch (factor) {
- case PixelBlendFactor::OTHERCOLOR:
- return source.rgb();
-
- case PixelBlendFactor::INVOTHERCOLOR:
- return Vec3::AssignToAll(255) - source.rgb();
-
- case PixelBlendFactor::SRCALPHA:
-#if defined(_M_SSE)
- return Vec3(_mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3)));
-#else
- return Vec3::AssignToAll(source.a());
-#endif
-
- case PixelBlendFactor::INVSRCALPHA:
-#if defined(_M_SSE)
- return Vec3(_mm_sub_epi32(_mm_set1_epi32(255), _mm_shuffle_epi32(source.ivec, _MM_SHUFFLE(3, 3, 3, 3))));
-#else
- return Vec3::AssignToAll(255 - source.a());
-#endif
-
- case PixelBlendFactor::DSTALPHA:
- return Vec3::AssignToAll(dst.a());
-
- case PixelBlendFactor::INVDSTALPHA:
- return Vec3::AssignToAll(255 - dst.a());
-
- case PixelBlendFactor::DOUBLESRCALPHA:
- return Vec3::AssignToAll(2 * source.a());
-
- case PixelBlendFactor::DOUBLEINVSRCALPHA:
- return Vec3::AssignToAll(255 - std::min(2 * source.a(), 255));
-
- case PixelBlendFactor::DOUBLEDSTALPHA:
- return Vec3::AssignToAll(2 * dst.a());
-
- case PixelBlendFactor::DOUBLEINVDSTALPHA:
- return Vec3