Compare commits

...
17 Commits
Author SHA1 Message Date
JordanTheToaster 4e9c5dcc88 Flatpak: Update KDE runtime to 6.11 2026-09-05 19:53:07 +02:00
PCSX2 Bot 450a39a37c [ci skip] Qt: Update Base Translation. 2026-09-05 02:08:01 +02:00
Tundrak ca0145e6aa InputRecording: Reset controller state after input playback 2026-09-04 11:20:01 -04:00
Tundrak 20c51dd80e Pad: Add functions to reset controller inputs 2026-09-04 11:20:01 -04:00
lightningterror aaf03c18b6 GS/TC: Fix -Wsign-compare warning. 2026-09-04 13:35:32 +02:00
TJnotJT 77bec5e0f5 GS/HW: Rewrite vertices if large ST.
Adds vertex rewriting functionality to both HW/SW. Aggressive version is user hack. Adds a GameDB hack for Ridge Racer V and Destruction Derby Arena. For SW, a less aggressive version is used if the hack is not enabled.
2026-09-04 12:05:30 +02:00
refractionpcsx2 f0cd834ed2 GS/HW: Improve source channel requirements for source lookups 2026-09-04 11:44:35 +02:00
TJnotJT 77cfac7dbe GS:HW: Do ROV channel masking with FBMask. 2026-09-04 11:39:30 +02:00
refractionpcsx2 ca5942151a Counters: Remove some magic numbers and use default framerates 2026-09-04 11:10:37 +02:00
refractionpcsx2 0be8947a33 Counters: Make sure uninitialized blank calculations use correct timing 2026-09-04 11:10:37 +02:00
refractionpcsx2 33127ad0df Core: Init video mode based on disc, bios or default NTSC, where available 2026-09-04 11:10:37 +02:00
PCSX2 Bot c475c752bf [ci skip] Qt: Update Base Translation. 2026-09-04 02:04:37 +02:00
TJnotJT bd9b6daaad GS:HW: Reduce primid max value.
Large values may cause errors in primid DATE on Mesa OpenGL.
2026-09-04 01:37:00 +02:00
TJnotJT 9120e6fb6a GS:HW: Allow dumping PrimID texture with RT dumping. 2026-09-04 01:37:00 +02:00
TellowKrinkle 65dc286e9b GS:VK: Use swizzle value compatible with MoltenVK on older GPUs 2026-09-03 19:20:31 +02:00
TellowKrinkle 4545e24f7d GHActions:MacOS: Add MoltenVK patch for older GPUs 2026-09-03 19:20:31 +02:00
Mrlinkwii deb6edc076 Github : update issue types
[ci skip]
2026-09-03 17:51:34 +02:00
57 changed files with 2475 additions and 1920 deletions
@@ -3,6 +3,7 @@ name: Application Bug Report
description: Found a problem with the application itself (ie. bad file path handling, UX issue)? Help us improve it.
title: "[BUG]: "
labels: [Bug]
type: Bug
# assignees:
# - octocat
body:
@@ -3,6 +3,7 @@ name: Emulation Bug Report
description: Problem in a game (ie. graphical artifacts, crashes)? Help us improve it.
title: "[BUG]: "
labels: [Bug]
type: Bug
# assignees:
# - octocat
body:
@@ -3,6 +3,7 @@ name: Feature request
description: Suggest a new feature or improve an existing one
title: "[Feature Request]: "
labels: ["Enhancement / Feature Request", "FR: Awaiting Consideration"]
type: Feature Request
# assignees:
# - octocat
body:
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
name: ${{ inputs.jobName }}
runs-on: ${{ inputs.os }}
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.9
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.10
options: --privileged
timeout-minutes: 60
@@ -1,15 +1,15 @@
{
"app-id": "net.pcsx2.PCSX2",
"runtime": "org.kde.Platform",
"runtime-version": "6.10",
"runtime-version": "6.11",
"sdk": "org.kde.Sdk",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.llvm20"
"org.freedesktop.Sdk.Extension.llvm22"
],
"add-extensions": {
"org.freedesktop.Platform.ffmpeg-full": {
"directory": "lib/ffmpeg",
"version": "25.08",
"version": "26.08",
"add-ld-path": ".",
"autodownload": true
}
@@ -52,8 +52,8 @@
"-DCMAKE_PREFIX_PATH=\"${FLATPAK_DEST}\"",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON",
"-DCMAKE_C_COMPILER=/usr/lib/sdk/llvm20/bin/clang",
"-DCMAKE_CXX_COMPILER=/usr/lib/sdk/llvm20/bin/clang++",
"-DCMAKE_C_COMPILER=/usr/lib/sdk/llvm22/bin/clang",
"-DCMAKE_CXX_COMPILER=/usr/lib/sdk/llvm22/bin/clang++",
"-DCMAKE_EXE_LINKER_FLAGS_INIT=-fuse-ld=lld",
"-DCMAKE_MODULE_LINKER_FLAGS_INIT=-fuse-ld=lld",
"-DCMAKE_SHARED_LINKER_FLAGS_INIT=-fuse-ld=lld",
@@ -0,0 +1,159 @@
From 4cf93f0f16b1503580cdb7ffdedb056321446677 Mon Sep 17 00:00:00 2001
From: squidbus <175574877+squidbus@users.noreply.github.com>
Date: Sat, 9 May 2026 16:53:22 -0700
Subject: [PATCH] Restore some checks for Mac1 GPUs.
---
MoltenVK/MoltenVK/GPUObjects/MVKDevice.h | 1 +
MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm | 34 +++++++++++--------
MoltenVK/MoltenVK/GPUObjects/MVKImage.mm | 2 +-
.../MoltenVK/GPUObjects/MVKPixelFormats.mm | 2 +-
4 files changed, 23 insertions(+), 16 deletions(-)
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index ba3914de5..8923d23f0 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -112,6 +112,7 @@ typedef struct MVKMTLDeviceCapabilities {
bool supportsApple8;
bool supportsApple9;
bool supportsApple10;
+ bool supportsMac1;
bool supportsMac2;
bool supportsMetal3;
bool supportsMetal4;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 2c930b004..2e212645a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -110,7 +110,8 @@
#if MVK_XCODE_26
supportsApple10 = supportsGPUFam(Apple10, mtlDev);
#endif
- supportsMac2 = MVK_MACOS; // Incl Mac2 & MacCatalyst2
+ supportsMac1 = MVK_MACOS;
+ supportsMac2 = supportsGPUFam(Mac2, mtlDev) || supportsGPUFam(MacCatalyst2, mtlDev);
isAppleGPU = supportsApple1;
@@ -1547,7 +1548,7 @@
const auto placementHeapFlags = VK_IMAGE_CREATE_2D_VIEW_COMPATIBLE_BIT_EXT |
VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT |
VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT;
- if (!getMVKConfig().useMTLHeap && mvkIsAnyFlagEnabled(flags, placementHeapFlags)) {
+ if (!_metalFeatures.placementHeaps && mvkIsAnyFlagEnabled(flags, placementHeapFlags)) {
return VK_ERROR_FORMAT_NOT_SUPPORTED;
}
@@ -2456,9 +2457,10 @@
// AMD support for MTLHeap is buggy.
auto cfgUseMTLHeap = getMVKConfig().useMTLHeap;
- _metalFeatures.placementHeaps = (_properties.vendorID == kAMDVendorId
- ? cfgUseMTLHeap == MVK_CONFIG_USE_MTLHEAP_ALWAYS
- : cfgUseMTLHeap != MVK_CONFIG_USE_MTLHEAP_NEVER);
+ auto allowMTLHeap = (_properties.vendorID == kAMDVendorId
+ ? cfgUseMTLHeap == MVK_CONFIG_USE_MTLHEAP_ALWAYS
+ : cfgUseMTLHeap != MVK_CONFIG_USE_MTLHEAP_NEVER);
+ _metalFeatures.placementHeaps = allowMTLHeap && (supportsMTLGPUFamily(Apple2) || supportsMTLGPUFamily(Mac2));
_metalFeatures.multisampleArrayTextures = !MVK_TVOS || mvkOSVersionIsAtLeast(16.0);
// Dynamic vertex stride needs to have everything aligned - compiled with support for vertex stride calls, and supported by both runtime OS and GPU.
@@ -2467,7 +2469,7 @@
_metalFeatures.renderLinearTextures = _gpuCapabilities.supportsRenderLinearTextures;
- if (supportsMTLGPUFamily(Mac2)) {
+ if (supportsMTLGPUFamily(Mac1)) {
_metalFeatures.mtlBufferAlignment = 256;
_metalFeatures.mtlConstantBufferAlignment = 32;
_metalFeatures.mtlCopyBufferAlignment = 4;
@@ -2489,15 +2491,18 @@
_metalFeatures.presentModeImmediate = true;
_metalFeatures.nonUniformThreadgroups = true;
_metalFeatures.native3DCompressedTextures = true;
- _metalFeatures.multisampleLayeredRendering = _metalFeatures.layeredRendering;
+ _metalFeatures.simdPermute = true;
+ _metalFeatures.memoryBarriers = true;
+ }
+
+ if (supportsMTLGPUFamily(Mac2)) {
+ _metalFeatures.multisampleLayeredRendering = true;
_metalFeatures.stencilFeedback = true;
_metalFeatures.depthResolve = true;
_metalFeatures.stencilResolve = true;
- _metalFeatures.simdPermute = true;
_metalFeatures.quadPermute = true;
_metalFeatures.simdReduction = true;
- _metalFeatures.memoryBarriers = true;
- }
+ }
if (supportsMTLGPUFamily(Apple1)) {
_metalFeatures.mtlBufferAlignment = 64;
@@ -2798,7 +2803,7 @@
_features.shaderResourceMinLod = true;
}
- if (supportsMTLGPUFamily(Mac2)) {
+ if (supportsMTLGPUFamily(Mac1)) {
_features.occlusionQueryPrecise = true;
_features.imageCubeArray = true;
_features.tessellationShader = true;
@@ -2970,19 +2975,19 @@
if (supportsMTLGPUFamily(Apple3)) {
_properties.limits.optimalBufferCopyOffsetAlignment = 16;
- } else if (supportsMTLGPUFamily(Mac2)) {
+ } else if (supportsMTLGPUFamily(Mac1)) {
_properties.limits.optimalBufferCopyOffsetAlignment = 256;
} else {
_properties.limits.optimalBufferCopyOffsetAlignment = 64;
}
- if (supportsMTLGPUFamily(Apple4) || supportsMTLGPUFamily(Mac2)) {
+ if (supportsMTLGPUFamily(Apple4) || supportsMTLGPUFamily(Mac1)) {
_properties.limits.maxFragmentInputComponents = 124;
} else {
_properties.limits.maxFragmentInputComponents = 60;
}
- if (supportsMTLGPUFamily(Apple5) || supportsMTLGPUFamily(Mac2)) {
+ if (supportsMTLGPUFamily(Apple5) || supportsMTLGPUFamily(Mac1)) {
_properties.limits.maxTessellationGenerationLevel = 64;
_properties.limits.maxTessellationPatchSize = 32;
} else if (supportsMTLGPUFamily(Apple3)) {
@@ -3653,6 +3658,7 @@ static uint32_t mvkGetEntryProperty(io_registry_entry_t entry, CFStringRef prope
if (supportsMTLGPUFamily(Apple2)) { logMsg += "\n\t\tGPU Family Apple 2"; } else
if (supportsMTLGPUFamily(Apple1)) { logMsg += "\n\t\tGPU Family Apple 1"; }
+ if (supportsMTLGPUFamily(Mac1)) { logMsg += "\n\t\tGPU Family Mac 1"; }
if (supportsMTLGPUFamily(Mac2)) { logMsg += "\n\t\tGPU Family Mac 2"; }
logMsg += "\n\t\tRead-Write Texture Tier ";
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index d2fdd4020..696ec4467 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -1382,7 +1382,7 @@ static MTLRegion getMTLRegion(const ImgRgn& imgRgn) {
const auto placementHeapFlags = VK_IMAGE_CREATE_2D_VIEW_COMPATIBLE_BIT_EXT |
VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT |
VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT;
- if (!getMVKConfig().useMTLHeap && mvkIsAnyFlagEnabled(pCreateInfo->flags, placementHeapFlags)) {
+ if (!getMetalFeatures().placementHeaps && mvkIsAnyFlagEnabled(pCreateInfo->flags, placementHeapFlags)) {
setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : MTLHeap must be enabled to create 2D-on-3D or block texel view compatible images."));
}
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
index d16d6c8f6..11ca10a03 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
@@ -1343,7 +1343,7 @@
addMTLVertexFormatDesc( UChar4Normalized_BGRA );
- if (gpuCaps.supportsApple5 || gpuCaps.supportsMac2) {
+ if (gpuCaps.supportsApple5 || gpuCaps.supportsMac1) {
addMTLVertexFormatDesc( FloatRG11B10 );
addMTLVertexFormatDesc( FloatRGB9E5 );
}
@@ -289,6 +289,8 @@ echo "Installing MoltenVK..."
rm -fr "MoltenVK-${MOLTENVK}"
tar xf "MoltenVK-$MOLTENVK.tar.gz"
cd "MoltenVK-${MOLTENVK}"
patch -p1 < "$SCRIPTDIR/4cf93f0f16b1503580cdb7ffdedb056321446677.patch"
patch -p1 < "$SCRIPTDIR/mvk-texture-swizzle.patch"
./fetchDependencies --macos
make macos MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0 MVK_CONFIG_USE_METAL_PRIVATE_API=1
cp Package/Latest/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib "$INSTALLDIR/lib/"
@@ -78,7 +78,7 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 libpng-$LIBPNG-apng.patch.gz
6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e libjpeg-turbo-$LIBJPEGTURBO.tar.gz
cf38e0e28c7e5605942c4a77755349b0145804a397af37eb1fb4c77cb237f635 ffmpeg-$FFMPEG.tar.xz
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c v$MOLTENVK.tar.gz
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c MoltenVK-$MOLTENVK.tar.gz
5cd9495d9316cf6cc937bb328a7fd491c3248ef2469cfa42511f1039f6148aca KDDockWidgets-$KDDOCKWIDGETS.tar.gz
7bd4e79ce18b1d47517e7e91fbb7cf19d4f01942804a519bc7c0bf32b6325dd5 plutovg-$PLUTOVG.tar.gz
78561b571ac224030cdc450ca2986b4de915c2ba7616004a6d71a379bffd15f3 plutosvg-$PLUTOSVG.tar.gz
@@ -102,7 +102,7 @@ if ! shasum -sa 256 --check SHASUMS 2> /dev/null; then
-O "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/$LIBJPEGTURBO/libjpeg-turbo-$LIBJPEGTURBO.tar.gz" \
-O "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-$LIBWEBP.tar.gz" \
-O "https://ffmpeg.org/releases/ffmpeg-$FFMPEG.tar.xz" \
-O "https://github.com/KhronosGroup/MoltenVK/archive/refs/tags/v$MOLTENVK.tar.gz" \
-O "https://github.com/KhronosGroup/MoltenVK/archive/v$MOLTENVK/MoltenVK-$MOLTENVK.tar.gz" \
-O "https://download.qt.io/archive/qt/${QT%.*}/$QT/submodules/qtbase-everywhere-src-$QT.tar.xz" \
-O "https://download.qt.io/archive/qt/${QT%.*}/$QT/submodules/qtimageformats-everywhere-src-$QT.tar.xz" \
-O "https://download.qt.io/archive/qt/${QT%.*}/$QT/submodules/qtsvg-everywhere-src-$QT.tar.xz" \
@@ -228,8 +228,10 @@ cd ..
# MoltenVK already builds universal binaries, nothing special to do here.
echo "Installing MoltenVK..."
rm -fr "MoltenVK-${MOLTENVK}"
tar xf "v$MOLTENVK.tar.gz"
tar xf "MoltenVK-$MOLTENVK.tar.gz"
cd "MoltenVK-${MOLTENVK}"
patch -p1 < "$SCRIPTDIR/4cf93f0f16b1503580cdb7ffdedb056321446677.patch"
patch -p1 < "$SCRIPTDIR/mvk-texture-swizzle.patch"
sed -i '' 's/xcodebuild "$@"/xcodebuild $XCODEBUILD_EXTRA_ARGS "$@"/g' fetchDependencies
sed -i '' 's/XCODEBUILD :=/XCODEBUILD ?=/g' Makefile
XCODEBUILD_EXTRA_ARGS="VALID_ARCHS=x86_64" ./fetchDependencies --macos
@@ -0,0 +1,139 @@
From f6be31a110761ea494a938d62473abc085ea447b Mon Sep 17 00:00:00 2001
From: TellowKrinkle <tellowkrinkle@gmail.com>
Date: Mon, 31 Aug 2026 02:52:38 -0500
Subject: [PATCH] Re-add native texture swizzle checks
---
MoltenVK/MoltenVK/API/mvk_deprecated_api.h | 2 +-
MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm | 3 +-
MoltenVK/MoltenVK/GPUObjects/MVKImage.mm | 51 ++++++++++++++++++-
.../MoltenVK/GPUObjects/MVKPixelFormats.mm | 3 ++
4 files changed, 56 insertions(+), 3 deletions(-)
diff --git a/MoltenVK/MoltenVK/API/mvk_deprecated_api.h b/MoltenVK/MoltenVK/API/mvk_deprecated_api.h
index 74fef844..8d16f86c 100644
--- a/MoltenVK/MoltenVK/API/mvk_deprecated_api.h
+++ b/MoltenVK/MoltenVK/API/mvk_deprecated_api.h
@@ -118,7 +118,7 @@ typedef struct {
VkBool32 fences; /**< If true, Metal synchronization fences (MTLFence) are supported. Deprecated. Will always be true on all platforms. */
VkBool32 rasterOrderGroups; /**< If true, Raster order groups in fragment shaders are supported. */
VkBool32 native3DCompressedTextures; /**< If true, 3D compressed images are supported natively, without manual decompression. */
- VkBool32 nativeTextureSwizzle; /**< If true, component swizzle is supported natively, without manual swizzling in shaders. Deprecated. Will always be true on all platforms. */
+ VkBool32 nativeTextureSwizzle; /**< If true, component swizzle is supported natively, without manual swizzling in shaders. */
VkBool32 placementHeaps; /**< If true, MTLHeap objects support placement of resources. */
VkDeviceSize pushConstantSizeAlignment; /**< The alignment used internally when allocating memory for push constants. Must be PoT. */
uint32_t maxTextureLayers; /**< The maximum number of layers in an array texture. */
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index aaf5945b..c7da8256 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -2496,6 +2496,7 @@
_metalFeatures.stencilResolve = true;
_metalFeatures.quadPermute = true;
_metalFeatures.simdReduction = true;
+ _metalFeatures.nativeTextureSwizzle = true;
}
if (supportsMTLGPUFamily(Apple1)) {
@@ -2504,6 +2505,7 @@
_metalFeatures.maxPerStageTextureCount = 31;
_metalFeatures.maxTextureDimension = (8 * KIBI);
_metalFeatures.maxQueryBufferSize = (64 * KIBI);
+ _metalFeatures.nativeTextureSwizzle = true;
_metalFeatures.maxPerStageDynamicMTLBufferCount = _metalFeatures.maxPerStageBufferCount;
_metalFeatures.renderLinearTextures = true;
@@ -2727,7 +2729,6 @@
_metalFeatures.events = true;
_metalFeatures.ioSurfaces = true;
_metalFeatures.renderWithoutAttachments = true;
- _metalFeatures.nativeTextureSwizzle = true;
}
bool MVKPhysicalDevice::isTier2MetalArgumentBuffers() {
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index 8eda5515..392a4f98 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -1925,7 +1925,7 @@ static void signalAndUntrack(const MVKSwapchainSignaler& signaler) {
}
id<MTLTexture> texView = nil;
- if (_useSwizzle) {
+ if (_useSwizzle && getMetalFeatures().nativeTextureSwizzle) {
texView = [mtlTex newTextureViewWithPixelFormat: _mtlPixFmt
textureType: _imageView->_mtlTextureType
levels: levelRange
@@ -2103,6 +2103,55 @@ static void signalAndUntrack(const MVKSwapchainSignaler& signaler) {
}
}
+#define SWIZZLE_MATCHES(R, G, B, A) mvkVkComponentMappingsMatch(_componentSwizzle, {VK_COMPONENT_SWIZZLE_ ##R, VK_COMPONENT_SWIZZLE_ ##G, VK_COMPONENT_SWIZZLE_ ##B, VK_COMPONENT_SWIZZLE_ ##A} )
+#define VK_COMPONENT_SWIZZLE_ANY VK_COMPONENT_SWIZZLE_MAX_ENUM
+
+ if (_imageView->_image->hasPixelFormatView(_planeIndex)) {
+ switch (_mtlPixFmt) {
+ case MTLPixelFormatR8Unorm:
+ if (SWIZZLE_MATCHES(ZERO, ANY, ANY, R)) {
+ _mtlPixFmt = MTLPixelFormatA8Unorm;
+ return VK_SUCCESS;
+ }
+ break;
+
+ case MTLPixelFormatA8Unorm:
+ if (SWIZZLE_MATCHES(A, ANY, ANY, ZERO)) {
+ _mtlPixFmt = MTLPixelFormatR8Unorm;
+ return VK_SUCCESS;
+ }
+ break;
+
+ case MTLPixelFormatRGBA8Unorm:
+ if (SWIZZLE_MATCHES(B, G, R, A)) {
+ _mtlPixFmt = MTLPixelFormatBGRA8Unorm;
+ return VK_SUCCESS;
+ }
+ break;
+
+ case MTLPixelFormatRGBA8Unorm_sRGB:
+ if (SWIZZLE_MATCHES(B, G, R, A)) {
+ _mtlPixFmt = MTLPixelFormatBGRA8Unorm_sRGB;
+ return VK_SUCCESS;
+ }
+ break;
+
+ case MTLPixelFormatBGRA8Unorm:
+ if (SWIZZLE_MATCHES(B, G, R, A)) {
+ _mtlPixFmt = MTLPixelFormatRGBA8Unorm;
+ return VK_SUCCESS;
+ }
+ break;
+
+ case MTLPixelFormatBGRA8Unorm_sRGB:
+ if (SWIZZLE_MATCHES(B, G, R, A)) {
+ _mtlPixFmt = MTLPixelFormatRGBA8Unorm_sRGB;
+ return VK_SUCCESS;
+ }
+ break;
+ }
+ }
+
_useSwizzle = !mvkVkComponentMappingsMatch(_componentSwizzle, {VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A});
return VK_SUCCESS;
}
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
index 316d7c6f..31a2c6a1 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm
@@ -654,6 +654,9 @@
bool pfv = false;
+ // Swizzle emulation may need to reinterpret
+ needsReinterpretation |= !_physicalDevice->getMetalFeatures()->nativeTextureSwizzle;
+
pfv |= isColorFormat && needsReinterpretation &&
mvkIsAnyFlagEnabled(vkImageUsageFlags, (VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_STORAGE_BIT |
--
2.39.5 (Apple Git-154)
+5
View File
@@ -4706,6 +4706,7 @@ SCES-50000:
gpuPaletteConversion: 2 # Lots of CLUTs in large textures.
cpuSpriteRenderBW: 1 # Fixes car textures.
cpuSpriteRenderLevel: 1 # Needed for above.
rewriteLargeST: 1 # Fix reflections on cars.
SCES-50001:
name: "Tekken Tag Tournament"
region: "PAL-M5"
@@ -35387,6 +35388,7 @@ SLPM-60109:
gpuPaletteConversion: 2 # Lots of CLUTs in large textures.
cpuSpriteRenderBW: 1 # Fixes car textures.
cpuSpriteRenderLevel: 1 # Needed for above.
rewriteLargeST: 1 # Fix reflections on cars.
SLPM-60112:
name: "フレースヴェルグ [体験版]"
name-sort: "ふれーすヴぇるぐ [たいけんばん]"
@@ -54809,6 +54811,7 @@ SLPS-20001:
gpuPaletteConversion: 2 # Lots of CLUTs in large textures.
cpuSpriteRenderBW: 1 # Fixes car textures.
cpuSpriteRenderLevel: 1 # Needed for above.
rewriteLargeST: 1 # Fix reflections on cars.
SLPS-20002:
name: "撞球 ビリヤードマスター2"
name-sort: "どうきゅう びりやーどますたー2"
@@ -63501,6 +63504,7 @@ SLPS-71502:
gpuPaletteConversion: 2 # Lots of CLUTs in large textures.
cpuSpriteRenderBW: 1 # Fixes car textures.
cpuSpriteRenderLevel: 1 # Needed for above.
rewriteLargeST: 1 # Fix reflections on cars.
SLPS-72501:
name: "ファイナルファンタジーⅩ [MEGA HITS!]"
name-sort: "ふぁいなるふぁんたじー10 [MEGA HITS!]"
@@ -64503,6 +64507,7 @@ SLUS-20002:
gpuPaletteConversion: 2 # Lots of CLUTs in large textures.
cpuSpriteRenderBW: 1 # Fixes car textures.
cpuSpriteRenderLevel: 1 # Needed for above.
rewriteLargeST: 1 # Fix reflections on cars.
SLUS-20003:
name: "Portal Runner"
region: "NTSC-U"
+20 -8
View File
@@ -169,6 +169,11 @@ float depth32_to_depth24(float d)
return uint_to_depth24(depth_to_uint(d));
}
float4 primid_to_rgba8(float p)
{
return uint_to_rgba8(asuint(p));
}
#if defined(__ps_copy__)
OUTPUT_TYPE ps_copy(PS_INPUT input) : OUTPUT_SV
{
@@ -323,6 +328,13 @@ OUTPUT_TYPE ps_convert_depth32_depth24(PS_INPUT input) : OUTPUT_SV
}
#endif
#if defined(__ps_convert_primid_rgba8__)
OUTPUT_TYPE ps_convert_primid_rgba8(PS_INPUT input) : OUTPUT_SV
{
return primid_to_rgba8(sample_c(input.t));
}
#endif
#define SAMPLE_RGBA_DEPTH_BILN(CONVERT_FN) \
uint width, height; \
Texture.GetDimensions(width, height); \
@@ -650,9 +662,9 @@ float ps_primid_image_init_0(PS_INPUT input) : SV_Target
{
float c;
if ((127.5f / 255.0f) < sample_c(input.t).a) // < 0x80 pass (== 0x80 should not pass)
c = float(-1);
c = float(PRIMID_MIN);
else
c = float(0x7FFFFFFF);
c = float(PRIMID_MAX);
return c;
}
#endif
@@ -662,9 +674,9 @@ float ps_primid_image_init_1(PS_INPUT input) : SV_Target
{
float c;
if (sample_c(input.t).a < (127.5f / 255.0f)) // >= 0x80 pass
c = float(-1);
c = float(PRIMID_MIN);
else
c = float(0x7FFFFFFF);
c = float(PRIMID_MAX);
return c;
}
#endif
@@ -674,9 +686,9 @@ float ps_primid_image_init_2(PS_INPUT input) : SV_Target
{
float c;
if ((254.5f / 255.0f) < sample_c(input.t).a) // < 0x80 pass (== 0x80 should not pass)
c = float(-1);
c = float(PRIMID_MIN);
else
c = float(0x7FFFFFFF);
c = float(PRIMID_MAX);
return c;
}
#endif
@@ -686,9 +698,9 @@ float ps_primid_image_init_3(PS_INPUT input) : SV_Target
{
float c;
if (sample_c(input.t).a < (254.5f / 255.0f)) // >= 0x80 pass
c = float(-1);
c = float(PRIMID_MIN);
else
c = float(0x7FFFFFFF);
c = float(PRIMID_MAX);
return c;
}
#endif
+5 -3
View File
@@ -1097,8 +1097,11 @@ void ps_fbmask(inout float4 C, float2 pos_xy)
{
if (PS_FBMASK)
{
float multi = PS_COLCLIP_HW ? 65535.0f : 255.0f;
float4 RT = trunc(RtLoad(int2(pos_xy)) * multi + 0.1f);
float multi_rgb = PS_COLCLIP_HW ? 65535.0f : 255.0f;
float multi_a = PS_RTA_CORRECTION ? 128.0f : 255.0f;
float4 RT = RtLoad(int2(pos_xy));
RT.rgb = trunc(RT.rgb * multi_rgb + 0.1f);
RT.a = round(RT.a * multi_a);
C = (float4)(((uint4)C & ~FbMask) | ((uint4)RT & FbMask));
}
}
@@ -1594,7 +1597,6 @@ if (bad)
output.c1 = o_col1;
#endif
#elif PS_RETURN_COLOR_ROV
o_col0 = (FbMask == 0xFFu) ? RtLoad(input.p.xy) : o_col0; // channel masking
if (!rov_discard_color)
RtWrite(input.p.xy, o_col0);
#endif
+17 -5
View File
@@ -139,6 +139,11 @@ float depth32_to_depth24(float d)
return uint_to_depth24(depth_to_uint(d));
}
vec4 primid_to_rgba8(float p)
{
return uint_to_rgba8(floatBitsToUint(p));
}
#ifdef ps_copy
void ps_copy()
{
@@ -212,6 +217,13 @@ void ps_convert_depth32_depth24()
}
#endif
#ifdef ps_convert_primid_rgba8
void ps_convert_primid_rgba8()
{
OUTPUT = primid_to_rgba8(sample_c());
}
#endif
#define SAMPLE_RGBA_DEPTH_BILN(CONVERT_FN) \
ivec2 dims = textureSize(TextureSampler, 0); \
vec2 top_left_f = PSin_t * vec2(dims) - 0.5f; \
@@ -615,23 +627,23 @@ void ps_yuv()
void main()
{
o_col0 = vec4(0x7FFFFFFF);
o_col0 = vec4(PRIMID_MAX);
#ifdef ps_primid_image_init_0
if((127.5f / 255.0f) < sample_c().a) // < 0x80 pass (== 0x80 should not pass)
o_col0 = vec4(-1);
o_col0 = vec4(PRIMID_MIN);
#endif
#ifdef ps_primid_image_init_1
if(sample_c().a < (127.5f / 255.0f)) // >= 0x80 pass
o_col0 = vec4(-1);
o_col0 = vec4(PRIMID_MIN);
#endif
#ifdef ps_primid_image_init_2
if((254.5f / 255.0f) < sample_c().a) // < 0x80 pass (== 0x80 should not pass)
o_col0 = vec4(-1);
o_col0 = vec4(PRIMID_MIN);
#endif
#ifdef ps_primid_image_init_3
if(sample_c().a < (254.5f / 255.0f)) // >= 0x80 pass
o_col0 = vec4(-1);
o_col0 = vec4(PRIMID_MIN);
#endif
}
#endif
+5 -5
View File
@@ -932,11 +932,11 @@ void ps_fbmask(inout vec4 C)
{
// FIXME do I need special case for 16 bits
#if PS_FBMASK
#if PS_COLCLIP_HW == 1
vec4 RT = trunc(sample_from_rt() * 65535.0f);
#else
vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f);
#endif
float multi_rgb = PS_COLCLIP_HW != 0 ? 65535.0f : 255.0f;
float multi_a = PS_RTA_CORRECTION != 0 ? 128.0f : 255.0f;
vec4 RT = sample_from_rt();
RT.rgb = trunc(RT.rgb * multi_rgb + 0.1f);
RT.a = round(RT.a * multi_a);
C = vec4((uvec4(C) & ~FbMask) | (uvec4(RT) & FbMask));
#endif
}
+17 -5
View File
@@ -130,6 +130,11 @@ float depth32_to_depth24(float d)
return uint_to_depth24(depth_to_uint(d));
}
vec4 primid_to_rgba8(float p)
{
return uint_to_rgba8(floatBitsToUint(p));
}
#ifdef ps_copy
void ps_copy()
{
@@ -281,6 +286,13 @@ void ps_convert_depth32_depth24()
}
#endif
#ifdef ps_convert_primid_rgba8
void ps_convert_primid_rgba8()
{
OUTPUT = primid_to_rgba8(sample_c(v_tex));
}
#endif
#define SAMPLE_RGBA_DEPTH_BILN(CONVERT_FN) \
ivec2 dims = textureSize(samp0, 0); \
vec2 top_left_f = v_tex * vec2(dims) - 0.5f; \
@@ -628,23 +640,23 @@ void ps_yuv()
void main()
{
o_col0 = vec4(0x7FFFFFFF);
o_col0 = vec4(PRIMID_MAX);
#ifdef ps_primid_image_init_0
if((127.5f / 255.0f) < sample_c(v_tex).a) // < 0x80 pass (== 0x80 should not pass)
o_col0 = vec4(-1);
o_col0 = vec4(PRIMID_MIN);
#endif
#ifdef ps_primid_image_init_1
if(sample_c(v_tex).a < (127.5f / 255.0f)) // >= 0x80 pass
o_col0 = vec4(-1);
o_col0 = vec4(PRIMID_MIN);
#endif
#ifdef ps_primid_image_init_2
if((254.5f / 255.0f) < sample_c(v_tex).a) // < 0x80 pass (== 0x80 should not pass)
o_col0 = vec4(-1);
o_col0 = vec4(PRIMID_MIN);
#endif
#ifdef ps_primid_image_init_3
if(sample_c(v_tex).a < (254.5f / 255.0f)) // >= 0x80 pass
o_col0 = vec4(-1);
o_col0 = vec4(PRIMID_MIN);
#endif
}
#endif
+5 -7
View File
@@ -1448,11 +1448,11 @@ vec4 ps_color()
void ps_fbmask(inout vec4 C)
{
#if PS_FBMASK
#if PS_COLCLIP_HW == 1
vec4 RT = trunc(sample_from_rt() * 65535.0f);
#else
vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f);
#endif
float multi_rgb = PS_COLCLIP_HW != 0 ? 65535.0f : 255.0f;
float multi_a = PS_RTA_CORRECTION != 0 ? 128.0f : 255.0f;
vec4 RT = sample_from_rt();
RT.rgb = trunc(RT.rgb * multi_rgb + 0.1f);
RT.a = round(RT.a * multi_a);
C = vec4((uvec4(C) & ~FbMask) | (uvec4(RT) & FbMask));
#endif
}
@@ -1966,8 +1966,6 @@ void main()
// Writing back color (result already written to o_col0 for non-ROV)
#if PS_RETURN_COLOR_ROV
o_col0 = mix(o_col0, sample_from_rt(), equal(FbMask, uvec4(0xFFu))); // channel masking
if (!rov_discard_color)
imageStore(RtImageRov, ivec2(gl_FragCoord.xy), o_col0);
#endif
+1 -1
View File
@@ -104,7 +104,7 @@ disable_compiler_warnings_for_target(speex)
# Find the Qt components that we need.
if(ENABLE_QT_UI)
find_package(Qt6 6.10.1 COMPONENTS CoreTools Core GuiTools Gui WidgetsTools Widgets LinguistTools REQUIRED)
find_package(Qt6 6.11.1 COMPONENTS CoreTools Core GuiTools Gui WidgetsTools Widgets LinguistTools REQUIRED)
if(NOT WIN32 AND NOT APPLE)
if (Qt6_VERSION VERSION_GREATER_EQUAL 6.10.0)
@@ -271,6 +271,13 @@
</item>
<item row="7" column="0" colspan="2">
<layout class="QGridLayout" name="hwFixesLayout">
<item row="5" column="0">
<widget class="QCheckBox" name="rewriteLargeST">
<property name="text">
<string>Rewrite Large ST</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="drawBuffering">
<property name="text">
@@ -389,6 +396,7 @@
<tabstop>disableRenderFixes</tabstop>
<tabstop>readTCOnClose</tabstop>
<tabstop>estimateTextureRegion</tabstop>
<tabstop>rewriteLargeST</tabstop>
</tabstops>
<resources/>
<connections/>
@@ -155,6 +155,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.readTCOnClose, "EmuCore/GS", "UserHacks_ReadTCOnClose", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.estimateTextureRegion, "EmuCore/GS", "UserHacks_EstimateTextureRegion", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.drawBuffering, "EmuCore/GS", "UserHacks_DrawBuffering", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.rewriteLargeST, "EmuCore/GS", "UserHacks_RewriteLargeST", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.gpuPaletteConversion, "EmuCore/GS", "paltex", false);
connect(m_fixes.cpuSpriteRenderBW, &QComboBox::currentIndexChanged, this,
&GraphicsSettingsWidget::onCPUSpriteRenderBWChanged);
@@ -636,6 +637,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
dialog()->registerWidgetHelp(m_fixes.drawBuffering, tr("Draw Buffering"), tr("Unchecked"),
tr("Attempts to reduce draw calls in games which do heavy context switching for blending purposes."));
dialog()->registerWidgetHelp(m_fixes.rewriteLargeST, tr("Rewrite Large ST"), tr("Unchecked"),
tr("Rewrite large ST coordinates and clamp the values (mainly for Ridge Racer V)."));
}
// Upscaling Fixes tab
File diff suppressed because it is too large Load Diff
+7 -4
View File
@@ -468,7 +468,7 @@ u32 cdvdGetElfCRC(const std::string& path)
return elfo.GetCRC();
}
static CDVDDiscType GetPS2ElfName(IsoReader& isor, std::string* name, std::string* version, Error* error)
static CDVDDiscType GetPS2ElfName(IsoReader& isor, std::string* name, std::string* version, std::string* vmode, Error* error)
{
CDVDDiscType retype = CDVDDiscType::Other;
name->clear();
@@ -510,6 +510,7 @@ static CDVDDiscType GetPS2ElfName(IsoReader& isor, std::string* name, std::strin
else if (key == "VMODE")
{
DevCon.WriteLn(Color_Blue, fmt::format("(SYSTEM.CNF) Disc region type = {}", value));
*vmode = value;
}
else if (key == "VER")
{
@@ -574,15 +575,15 @@ static std::string ExecutablePathToSerial(const std::string& path)
return serial;
}
void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::string* out_version, u32* out_crc,
void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::string* out_version, std::string* out_region, u32* out_crc,
CDVDDiscType* out_disc_type)
{
Error error;
IsoReader isor;
std::string elfpath, version;
std::string elfpath, version, vmode;
CDVDDiscType disc_type = CDVDDiscType::Other;
if (!isor.Open(&error) || (disc_type = GetPS2ElfName(isor, &elfpath, &version, &error)) == CDVDDiscType::Other)
if (!isor.Open(&error) || (disc_type = GetPS2ElfName(isor, &elfpath, &version, &vmode, &error)) == CDVDDiscType::Other)
Console.Error(fmt::format("Failed to get ELF name: {}", error.GetDescription()));
// Don't bother parsing it if we don't need the CRC.
@@ -616,6 +617,8 @@ void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::st
*out_version = std::move(version);
if (out_disc_type)
*out_disc_type = disc_type;
if (out_region)
*out_region = vmode;
}
void cdvdReadKey(u8, u16, u32 arg2, u8* key)
+1 -1
View File
@@ -179,7 +179,7 @@ extern void cdvdNewDiskCB();
extern u8 cdvdRead(u8 key);
extern void cdvdWrite(u8 key, u8 rt);
extern void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::string* out_version, u32* out_crc,
extern void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::string* out_version, std::string* out_region, u32* out_crc,
CDVDDiscType* out_disc_type);
extern u32 cdvdGetElfCRC(const std::string& path);
extern bool cdvdLoadElf(ElfObject* elfo, const std::string_view elfpath, bool isPSXElf, Error* error);
+1 -1
View File
@@ -1263,7 +1263,7 @@ function(setup_main_executable target)
if("${file}" MATCHES "^\\.") # Don't copy macOS garbage (mainly Finder's .DS_Store files) into application
continue()
endif()
if (NOT WIN32 AND "${path}" MATCHES "/dx11/") # Don't include unneccessary stuff
if (NOT WIN32 AND "${path}" MATCHES "/dx11/") # Don't include unnecessary stuff
continue()
endif()
if (NOT BUNDLE_EMOJI_FONT AND "${path}" MATCHES "fonts/Twemoji")
+1
View File
@@ -811,6 +811,7 @@ struct Pcsx2Config
UserHacks_NativePaletteDraw : 1,
UserHacks_EstimateTextureRegion : 1,
UserHacks_DrawBuffering : 1,
UserHacks_RewriteLargeST : 1,
FXAA : 1,
ShadeBoost : 1,
DumpGSData : 1,
+6 -4
View File
@@ -215,7 +215,7 @@ static void vSyncInfoCalc(vSyncTimingInfo* info, double framesPerSecond, u32 sca
// Dynasty Warriors 3 Xtreme Legends - fake save corruption when loading save
// Jak II - random speedups
// Shadow of Rome - FMV audio issues
const bool ntsc_hblank = gsVideoMode != GS_VideoMode::PAL && gsVideoMode != GS_VideoMode::DVD_PAL;
const bool ntsc_hblank = gsVideoMode != GS_VideoMode::PAL && gsVideoMode != GS_VideoMode::DVD_PAL && (gsVideoMode != GS_VideoMode::Uninitialized || framesPerSecond != (Pcsx2Config::GSOptions::DEFAULT_FRAME_RATE_PAL / 2));
const u64 HalfFrame = Frame / 2;
const float extra_scanlines = static_cast<float>(IsProgressiveVideoMode()) * (ntsc_hblank ? 0.5f : 1.5f);
const u64 Blank = Scanline * ((ntsc_hblank ? 22.5f : 24.5f) + extra_scanlines);
@@ -307,10 +307,12 @@ double GetVerticalFrequency()
// https://web.archive.org/web/20120629231826fw_/http://ntsc-tv.com/index.html
// https://web.archive.org/web/20200831051302/https://www.hdretrovision.com/240p/
std::string cur_region = VMManager::Internal::GetCurrentRegion();
switch (gsVideoMode)
{
case GS_VideoMode::Uninitialized: // SetGsCrt hasn't executed yet, give some temporary values.
return 60.00;
return (cur_region == "PAL") ? Pcsx2Config::GSOptions::DEFAULT_FRAME_RATE_PAL : Pcsx2Config::GSOptions::DEFAULT_FRAME_RATE_NTSC;
case GS_VideoMode::PAL:
case GS_VideoMode::DVD_PAL:
return (IsProgressiveVideoMode() == false) ? EmuConfig.GS.FrameratePAL : EmuConfig.GS.FrameratePAL - 0.24f;
@@ -352,9 +354,9 @@ void UpdateVSyncRate(bool force)
{
case GS_VideoMode::Uninitialized: // SYSCALL instruction hasn't executed yet, give some temporary values.
if (gsIsInterlaced)
total_scanlines = SCANLINES_TOTAL_NTSC_I;
total_scanlines = (vertical_frequency == Pcsx2Config::GSOptions::DEFAULT_FRAME_RATE_PAL) ? SCANLINES_TOTAL_PAL_I : SCANLINES_TOTAL_NTSC_I;
else
total_scanlines = SCANLINES_TOTAL_NTSC_NI;
total_scanlines = (vertical_frequency == Pcsx2Config::GSOptions::DEFAULT_FRAME_RATE_PAL) ? SCANLINES_TOTAL_PAL_NI : SCANLINES_TOTAL_NTSC_NI;
break;
case GS_VideoMode::PAL:
case GS_VideoMode::DVD_PAL:
+2 -1
View File
@@ -174,7 +174,8 @@ The clamp modes are also numerically based.
* cpuSpriteRenderBW [Value between `0` to `10`] {Disabled, 1 (64), 2 (128), 3 (192), 4 (256), 5 (320), 6 (384), 7 (448), 8 (512), 9 (576), 10 (640)} Default: Off (`0`)
* cpuSpriteRenderLevel [`0` or `1` or `2`] {Sprites only, Sprites/Triangles, Blended Sprites/Triangles} Default: Off unless cpuSpriteRenderBW has value other than Off then it is 'Sprites only' (`0`)
* estimateTextureRegion [`0` or `1`] {Off, On} Default: Off (`0`)
* drawBuffering [`0` or `1`] {Off, On} Default: Off (`0`)
* drawBuffering [`0` or `1`] {Off, On} Default: Off (`0`)
* rewriteLargeST [`0` or `1`] {Off, On} Default: Off (`0`)
* getSkipCount {`GSC` with suffix } {None unless specific game GSC} Default: Disabled (`0`) unless valid variable name (ex. GSC_PolyphonyDigitalGames, GSC_UrbanReign, ...)
* gpuPaletteConversion [`0` or `1`] {Off, On} Default: Off (`0`)
* gpuTargetCLUT [`0` or `1` or `2`] {Disabled, Enabled (Exact Match), Enabled (Check Inside Target)} Default: Disabled (`0`)
+6 -1
View File
@@ -204,7 +204,12 @@
"minimum": 0,
"maximum": 1
},
"drawBuffering": {
"drawBuffering": {
"type": "integer",
"minimum": 0,
"maximum": 1
},
"rewriteLargeST": {
"type": "integer",
"minimum": 0,
"maximum": 1
+153
View File
@@ -6838,6 +6838,159 @@ GIFRegTEX0 GSState::GetTex0Layer(u32 lod)
return TEX0;
}
// Fix large ST coorindates that may cause graphical glitches.
template <u32 primclass>
void GSState::RewriteVerticesIfLargeSTImpl(const GSVector4& large_val, bool check_clamp_mode)
{
// Need to be texture mapping with ST and not have so many vertices that rewritten indices will overflow.
if (PRIM->TME && PRIM->FST == 0)
{
const float tw = static_cast<float>(1 << m_context->TEX0.TW);
const float th = static_cast<float>(1 << m_context->TEX0.TH);
const GSVector4 tsize = GSVector4(tw, th, 1.0f, 1.0f);
// Only rewrite big/small S or T when the clamping mode is CLAMP or REGION_CLAMP (if requested).
const GIFRegCLAMP& clamp = m_context->CLAMP;
const GSVector4 clamp_mask =
check_clamp_mode ?
GSVector4::cast(GSVector4i(
(clamp.WMS == CLAMP_CLAMP || clamp.WMS == CLAMP_REGION_CLAMP) ? 0xFFFFFFFF : 0,
(clamp.WMT == CLAMP_CLAMP || clamp.WMT == CLAMP_REGION_CLAMP) ? 0xFFFFFFFF : 0,
0,
0)) :
GSVector4::cast(GSVector4i::cxpr(0xFFFFFFFF));
const GSVector4 tex_bbox = m_vt.m_min.t.xyxy(m_vt.m_max.t) / tsize.xyxy();
const bool large_st = ((tex_bbox.abs() >= large_val) & clamp_mask).mask() || m_vt.nan.value;
if (large_st)
{
GL_PUSH("Large ST detected, rewriting vertices.");
if (m_index->tail > UINT16_MAX)
{
GL_INS("Too many vertices to rewrite safely, exiting.");
DevCon.Warning("Large ST detected but too many vertices to rewrite safely.");
return;
}
constexpr int n = GSUtil::GetClassVertexCount(primclass);
// Make sure the copy buffer is large enough.
while (m_max_vertex_count < m_index->tail)
GrowVertexBuffer();
GSVertex* RESTRICT vertex = m_vertex->buff;
GSVertex* RESTRICT vertex_copy = m_vertex->buff_copy;
u16* RESTRICT index = m_index->buff;
const int count = static_cast<int>(m_index->tail);
for (int i = 0; i < count; i += n)
{
GSVector4 stcq[n];
// Load STQ for this primitive.
for (int j = 0; j < n; j++)
stcq[j] = GSVector4::cast(GSVector4i(vertex[index[i + j]].m[0]));
// Perform Q division and see which values need to be rewritten.
GSVector4 uv[n];
GSVector4 small{}, big{}, nan{};
for (int j = 0; j < n; j++)
{
// For sprites always use Q of second vertex.
const GSVector4 q = primclass == GS_SPRITE_CLASS ? stcq[1].wwww() : stcq[j].wwww();
uv[j] = (stcq[j] / q).xyzw(GSVector4::zero());
small |= (uv[j] <= -large_val);
big |= (uv[j] >= large_val);
nan |= (uv[j] != uv[j]);
}
// Get the new values for fields that will be rewritten.
// The follows rules are used:
// 1. If there are small values but not big or nans, make all vertices small.
// 2. If there are big values but not small or nans, make all vertices big.
// 3. If there are both big and small values, or nans, make all vertices zero.
GSVector4 uv_new = GSVector4::zero();
uv_new = uv_new.blend32(-large_val, small);
uv_new = uv_new.blend32(large_val, big);
uv_new = uv_new.blend32(GSVector4::zero(), (small & big) | nan);
const GSVector4 rewrite = (((small | big) & clamp_mask) | nan).xyxy(GSVector4::zero());
// If both S and T are rewritten, no point in keeping Q. Just set it to 1.0f;
if ((rewrite.mask() & 3) == 3)
{
for (int j = 0; j < n; j++)
stcq[j] = stcq[j].template insert32<0, 3>(GSVector4::m_one);
}
// Rewrite the fields that require it and write to the copy buffer.
for (int j = 0; j < n; j++)
{
// For sprites always use Q of second vertex.
const GSVector4 q = (primclass == GS_SPRITE_CLASS) ? stcq[1].wwww() : stcq[j].wwww();
stcq[j] = stcq[j].blend32(uv_new * q, rewrite);
vertex_copy[i + j].m[0] = GSVector4i::cast(stcq[j]);
vertex_copy[i + j].m[1] = vertex[index[i + j]].m[1];
index[i + j] = i + j;
}
}
// Swap the buffers and fix the counts.
std::swap(m_vertex->buff, m_vertex->buff_copy);
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
// Recalculate ST min/max/eq in the vertex trace.
GSVector4 tmin = GSVector4::cxpr(FLT_MAX);
GSVector4 tmax = GSVector4::cxpr(-FLT_MAX);
for (int i = 0; i < count; i += n)
{
for (int j = 0; j < n; j++)
{
GSVector4 stcq = GSVector4::cast(GSVector4i(m_vertex->buff[i + j].m[0]));
const float q = (primclass == GS_SPRITE_CLASS) ? m_vertex->buff[i + 1].RGBAQ.Q : stcq.w;
stcq = (stcq / q).xyzw(stcq);
tmin = tmin.min(stcq);
tmax = tmax.max(stcq);
}
}
m_vt.m_min.t = tmin.xyww() * tsize;
m_vt.m_max.t = tmax.xyww() * tsize;
m_vt.m_eq.stq = (m_vt.m_min.t == m_vt.m_max.t).mask();
// Dump vertices if needed.
if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()) && GSConfig.SaveInfo)
{
DumpVertices(GetDrawDumpPath("%05d_vertices_st_rewrite.txt", s_n));
}
}
}
}
void GSState::RewriteVerticesIfLargeST(const GSVector4& large_val, bool check_clamp_mode)
{
switch (m_vt.m_primclass)
{
case GS_POINT_CLASS:
RewriteVerticesIfLargeSTImpl<GS_POINT_CLASS>(large_val, check_clamp_mode);
break;
case GS_LINE_CLASS:
RewriteVerticesIfLargeSTImpl<GS_LINE_CLASS>(large_val, check_clamp_mode);
break;
case GS_SPRITE_CLASS:
RewriteVerticesIfLargeSTImpl<GS_SPRITE_CLASS>(large_val, check_clamp_mode);
break;
case GS_TRIANGLE_CLASS:
RewriteVerticesIfLargeSTImpl<GS_TRIANGLE_CLASS>(large_val, check_clamp_mode);
break;
default:
pxFail("Invalid primclass"); // Impossible
break;
}
}
// GSTransferBuffer
GSState::GSTransferBuffer::GSTransferBuffer()
+3
View File
@@ -541,6 +541,9 @@ public:
bool SpriteDrawWithoutGaps();
void CalculatePrimitiveCoversWithoutGaps();
GIFRegTEX0 GetTex0Layer(u32 lod);
template <u32 primclass>
void RewriteVerticesIfLargeSTImpl(const GSVector4& large_val, bool check_clamp_mode);
void RewriteVerticesIfLargeST(const GSVector4& large_val, bool check_clamp_mode);
};
// We put this in the header because of Multi-ISA.
+2
View File
@@ -49,6 +49,7 @@ const char* ShaderEntryPoint(ShaderConvert value)
case ShaderConvert::RGB5A1_TO_DEPTH16: return "ps_convert_rgb5a1_depth16";
case ShaderConvert::DEPTH32_TO_DEPTH24: return "ps_convert_depth32_depth24";
case ShaderConvert::DEPTH_COPY: return "ps_depth_copy";
case ShaderConvert::PRIMID_TO_RGBA8: return "ps_convert_primid_rgba8";
case ShaderConvert::DOWNSAMPLE_COPY: return "ps_downsample_copy";
case ShaderConvert::RGBA_TO_8I: return "ps_convert_rgba_8i";
case ShaderConvert::RGB5A1_TO_8I: return "ps_convert_rgb5a1_8i";
@@ -109,6 +110,7 @@ const char* ShaderConvertName(ShaderConvert shader)
ENTRY(RGBA8_TO_DEPTH16);
ENTRY(RGB5A1_TO_DEPTH16);
ENTRY(DEPTH32_TO_DEPTH24);
ENTRY(PRIMID_TO_RGBA8);
ENTRY(DOWNSAMPLE_COPY);
ENTRY(RGBA_TO_8I);
ENTRY(RGB5A1_TO_8I);
+7
View File
@@ -59,6 +59,7 @@ enum class ShaderConvert
RGBA8_TO_DEPTH16,
RGB5A1_TO_DEPTH16,
DEPTH32_TO_DEPTH24,
PRIMID_TO_RGBA8,
DOWNSAMPLE_COPY,
RGBA_TO_8I,
RGB5A1_TO_8I,
@@ -122,6 +123,7 @@ static inline constexpr bool HasColorOutput(ShaderConvert shader)
case ShaderConvert::DEPTH32_TO_RGBA8:
case ShaderConvert::DEPTH32_TO_RGB8:
case ShaderConvert::DEPTH16_TO_RGB5A1:
case ShaderConvert::PRIMID_TO_RGBA8:
case ShaderConvert::DOWNSAMPLE_COPY:
case ShaderConvert::RGBA_TO_8I:
case ShaderConvert::RGB5A1_TO_8I:
@@ -162,6 +164,7 @@ static inline constexpr bool HasFloat32Input(ShaderConvert shader)
case ShaderConvert::DEPTH32_TO_RGB8:
case ShaderConvert::DEPTH16_TO_RGB5A1:
case ShaderConvert::DEPTH32_TO_DEPTH24:
case ShaderConvert::PRIMID_TO_RGBA8:
return true;
default:
return false;
@@ -533,6 +536,10 @@ static inline ShaderConvertSelector GetConvertShader(GSTexture::Format src, GSTe
pxAssert(false);
}
break;
case GSTexture::Format::PrimID:
pxAssert(dst == GSTexture::Format::Color);
shader = ShaderConvert::PRIMID_TO_RGBA8;
break;
default:
pxAssert(false);
break;
+11
View File
@@ -63,4 +63,15 @@ enum class PS_ROV_DEPTH : uint32_t
READ_ONLY = 2,
};
#if defined(__METAL_VERSION__)
#define CONSTANT constant
#else
#define CONSTANT
#endif
static constexpr CONSTANT int PRIMID_MAX = (1 << 24) - 1;
static constexpr CONSTANT int PRIMID_MIN = -1;
#undef CONSTANT
} // namespace GSShader
+1 -1
View File
@@ -44,7 +44,7 @@ bool GSTexture::Save(const std::string& fn)
{
// Depth textures need special treatment - we have a stencil component.
// Just re-use the existing conversion shader instead.
if (m_format == Format::DepthStencil || m_format == Format::DepthColor)
if (m_format == Format::DepthStencil || m_format == Format::DepthColor || m_format == Format::PrimID)
{
GSTexture* temp = g_gs_device->CreateRenderTarget(GetWidth(), GetHeight(), Format::Color, false);
if (!temp)
+4
View File
@@ -249,6 +249,8 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
ShaderMacro sm_ps;
sm_ps.AddMacro("PIXEL_SHADER", 1);
sm_ps.AddMacro("PRIMID_MAX", GSShader::PRIMID_MAX);
sm_ps.AddMacro("PRIMID_MIN", GSShader::PRIMID_MIN);
sm_ps.AddMacro("HAS_BILN", static_cast<int>(shader.Biln()));
sm_ps.AddMacro("HAS_STENCIL_OUTPUT", static_cast<int>(shader.StencilOutput()));
sm_ps.AddMacro("HAS_INTEGER_OUTPUT", static_cast<int>(shader.IntegerOutputBpp() != 0));
@@ -582,6 +584,8 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
const std::string entry_point_macro = WrapEntryPointMacro(entry_point);
ShaderMacro sm_ps;
sm_ps.AddMacro("PIXEL_SHADER", 1);
sm_ps.AddMacro("PRIMID_MAX", GSShader::PRIMID_MAX);
sm_ps.AddMacro("PRIMID_MIN", GSShader::PRIMID_MIN);
sm_ps.AddMacro(entry_point_macro.c_str(), 1);
m_date.primid_init_ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *convert_hlsl, sm_ps.GetPtr(), entry_point.c_str());
if (!m_date.primid_init_ps[i])
+4
View File
@@ -2877,6 +2877,8 @@ bool GSDevice12::CompileConvertPipelines()
ShaderMacro sm;
sm.AddMacro("PIXEL_SHADER", 1);
sm.AddMacro("PRIMID_MAX", GSShader::PRIMID_MAX);
sm.AddMacro("PRIMID_MIN", GSShader::PRIMID_MIN);
sm.AddMacro("HAS_BILN", static_cast<int>(shader.Biln()));
sm.AddMacro("HAS_STENCIL_OUTPUT", static_cast<int>(shader.StencilOutput()));
sm.AddMacro("HAS_INTEGER_OUTPUT", static_cast<int>(shader.IntegerOutputBpp() != 0));
@@ -2930,6 +2932,8 @@ bool GSDevice12::CompileConvertPipelines()
ShaderMacro sm;
sm.AddMacro("PIXEL_SHADER", "1");
sm.AddMacro("PRIMID_MAX", GSShader::PRIMID_MAX);
sm.AddMacro("PRIMID_MIN", GSShader::PRIMID_MIN);
sm.AddMacro(entry_point_macro.c_str(), "1");
ComPtr<ID3DBlob> ps(m_shader_cache.GetPixelShader(*source, sm.GetPtr(), entry_point.c_str()));
+19 -18
View File
@@ -2901,6 +2901,15 @@ void GSRendererHW::Draw()
// First pass texture shuffle detection using context/vertices.
DetectTextureShuffle();
// Ridge Racer V and Destruction Derby Arena have large ST coordinates in reflection map draws that cause
// speckled artifacts on cars. Detect and rewrite such vertices here.
if (GSConfig.UserHacks_RewriteLargeST)
{
// The threshold is chosen to be low enough to fix the artifacts in Ridge Racer V and Destruction Derby Arena
// while not breaking anything.
RewriteVerticesIfLargeST(GSVector4::cxpr(16.0f), false);
}
// When the format is 24bit (Z or C), DATE ceases to function.
// It was believed that in 24bit mode all pixels pass because alpha doesn't exist
@@ -3526,13 +3535,14 @@ void GSRendererHW::Draw()
const u32 color_mask = (m_vt.m_max.c > GSVector4i::zero()).mask();
const bool texture_function_color = m_cached_ctx.TEX0.TFX == TFX_DECAL || (color_mask & 0xFFF) || (m_cached_ctx.TEX0.TFX > TFX_DECAL && (color_mask & 0xF000));
const bool texture_function_alpha = m_cached_ctx.TEX0.TFX != TFX_MODULATE || (color_mask & 0xF000);
const bool req_color = (is_possible_channel_shuffle && channel_shuffle_targets != ChannelFetch_ALPHA) || (!is_possible_channel_shuffle && ((texture_function_color && (!PRIM->ABE || GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp < 16 || (NeedsBlending() && IsUsingCsInBlend())) && (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF))) || need_aem_color));
const bool alpha_used = (GSUtil::GetChannelMask(m_context->TEX0.PSM) == 0x8 || (m_context->TEX0.TCC && texture_function_alpha)) && ((NeedsBlending() && IsUsingAsInBlend()) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > ATST_ALWAYS) || (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0xFF000000)) != (fm_mask & 0xFF000000)));
const bool req_alpha = (is_possible_channel_shuffle && channel_shuffle_targets == ChannelFetch_ALPHA) || (!is_possible_channel_shuffle && (GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x8) && alpha_used);
const bool color_used = (is_possible_channel_shuffle && channel_shuffle_targets != ChannelFetch_ALPHA) || (!is_possible_channel_shuffle && ((texture_function_color && (!PRIM->ABE || (NeedsBlending() && IsUsingCsInBlend())) && ((m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF))) || need_aem_color));
const bool alpha_used = (is_possible_channel_shuffle && channel_shuffle_targets == ChannelFetch_ALPHA) || (!is_possible_channel_shuffle && (m_context->TEX0.TCC && texture_function_alpha) && ((NeedsBlending() && IsUsingAsInBlend()) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > ATST_ALWAYS) || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0xFF000000)) != (fm_mask & 0xFF000000)));
const bool req_color = (GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x7) && color_used;
const bool req_alpha = (GSUtil::GetChannelMask(m_context->TEX0.PSM) == 0x8 && (color_used || alpha_used)) || ((GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x8) && alpha_used);
// TODO: Be able to send an alpha of 1.0 (blended with vertex alpha maybe?) so we can avoid sending the texture, since we don't always need it.
// Example games: Evolution Snowboarding, Final Fantasy Dirge of Cerberus, Red Dead Revolver, Stuntman, Tony Hawk's Underground 2, Ultimate Spider-Man.
if (!req_color && !alpha_used)
if (!req_color && !req_alpha)
{
m_process_texture = false;
possible_shuffle = false;
@@ -7689,26 +7699,17 @@ void GSRendererHW::ConfigureROV(bool color_rov, bool depth_rov)
if (color_rov)
{
// FbMask setup
if (m_conf.colormask.wrgba != 0)
if (m_conf.colormask.wrgba != 0 && m_conf.colormask.wrgba != 0xF)
{
if (!m_conf.ps.fbmask)
m_conf.cb_ps.FbMask = GSVector4i::zero();
const GSVector4i fbmask = GSVector4i(m_conf.colormask.wr ? 0 : 0xFF, m_conf.colormask.wg ? 0 : 0xFF,
m_conf.colormask.wb ? 0 : 0xFF, m_conf.colormask.wa ? 0 : 0xFF);
if (!m_conf.ps.fbmask)
{
// Don't enable FB mask emulation, just use the mask for ROV.
m_conf.cb_ps.FbMask = fbmask;
}
else
{
m_conf.cb_ps.FbMask |= fbmask;
}
m_conf.ps.fbmask = true;
m_conf.cb_ps.FbMask |= fbmask;
GL_INS("ROV: FbMask={R=%x, G=%x, B=%x, A=%x}",
m_conf.cb_ps.FbMask.r, m_conf.cb_ps.FbMask.g, m_conf.cb_ps.FbMask.b, m_conf.cb_ps.FbMask.a);
}
else
{
m_conf.ps.no_color = true;
}
// Blend setup
if (m_conf.IsBlending())
+2 -2
View File
@@ -1735,8 +1735,8 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
match = false;
// Situations where font has been uploaded to the alpha channel and they use a different buffer width, this can't currently be translated correctly.
GSLocalMemory::psm_t req_psm = GSLocalMemory::m_psm[psm];
if (!possible_shuffle && req_psm.trbpp <= 8 && req_psm.bpp == 32 && t->m_TEX0.TBW != bw && (bw * 64) != t->m_valid.z && r.w > req_psm.pgs.y)
const GSLocalMemory::psm_t req_psm = GSLocalMemory::m_psm[psm];
if (!possible_shuffle && req_psm.trbpp <= 8 && req_psm.bpp == 32 && t->m_TEX0.TBW != bw && static_cast<int>(bw * 64) != t->m_valid.z && r.w > req_psm.pgs.y)
match = false;
// Different swizzle, different width, and dirty, so probably not what we want.
+14 -9
View File
@@ -112,22 +112,22 @@ fragment void ps_datm0(float4 p [[position]], DirectReadTextureIn<float> tex)
fragment float4 ps_primid_init_datm1(float4 p [[position]], DirectReadTextureIn<float> tex)
{
return tex.read(p).a < (127.5f / 255.f) ? -1 : FLT_MAX;
return tex.read(p).a < (127.5f / 255.f) ? GSShader::PRIMID_MIN : GSShader::PRIMID_MAX;
}
fragment float4 ps_primid_init_datm0(float4 p [[position]], DirectReadTextureIn<float> tex)
{
return tex.read(p).a > (127.5f / 255.f) ? -1 : FLT_MAX;
return tex.read(p).a > (127.5f / 255.f) ? GSShader::PRIMID_MIN : GSShader::PRIMID_MAX;
}
fragment float4 ps_primid_rta_init_datm1(float4 p [[position]], DirectReadTextureIn<float> tex)
{
return tex.read(p).a < (254.5f / 255.f) ? -1 : FLT_MAX;
return tex.read(p).a < (254.5f / 255.f) ? GSShader::PRIMID_MIN : GSShader::PRIMID_MAX;
}
fragment float4 ps_primid_rta_init_datm0(float4 p [[position]], DirectReadTextureIn<float> tex)
{
return tex.read(p).a > (254.5f / 255.f) ? -1 : FLT_MAX;
return tex.read(p).a > (254.5f / 255.f) ? GSShader::PRIMID_MIN : GSShader::PRIMID_MAX;
}
fragment float4 ps_rta_correction(ConvertShaderData data [[stage_in]], ConvertPSRes res)
@@ -175,11 +175,6 @@ fragment float4 ps_convert_depth16_rgb5a1(ConvertShaderData data [[stage_in]], C
return convert_depth16_rgba8(res.sample(data.t)) / 255.f;
}
fragment float ps_convert_float32_depth_to_color(ConvertShaderData data [[stage_in]], ConvertPSDepthRes res)
{
return res.sample(data.t);
}
fragment float4 ps_downsample_copy(ConvertShaderData data [[stage_in]],
texture2d<float> texture [[texture(GSMTLTextureIndexNonHW)]],
constant GSMTLDownsamplePSUniform& uniform [[buffer(GSMTLBufferIndexUniforms)]])
@@ -273,6 +268,16 @@ struct ConvertToDepthRes
}
};
static float4 primid_to_rgba8(float p)
{
return float4(as_type<uchar4>(p)) / 255.f;
}
fragment float4 ps_convert_primid_rgba8(ConvertShaderData data [[stage_in]], ConvertPSDepthOrColorRes res)
{
return primid_to_rgba8(res.sample(data.t));
}
fragment DepthOrColorOut ps_depth_copy(ConvertShaderData data [[stage_in]], ConvertPSDepthOrColorRes res)
{
return res.sample(data.t);
+7 -5
View File
@@ -1304,8 +1304,12 @@ struct PSMain
{
if (PS_FBMASK)
{
float multi = PS_COLCLIP_HW ? 65535.0 : 255.5;
C = float4((uint4(int4(C)) & (cb.fbmask ^ 0xff)) | (uint4(current_color * float4(multi, multi, multi, 255)) & cb.fbmask));
float multi_rgb = PS_COLCLIP_HW ? 65535.0 : 255.5;
float multi_a = PS_RTA_CORRECTION ? 128.0 : 255.0;
float4 RT = current_color;
RT.rgb = RT.rgb * multi_rgb;
RT.a = round(RT.a * multi_a);
C = float4((uint4(int4(C)) & (cb.fbmask ^ 0xff)) | (uint4(RT) & cb.fbmask));
}
}
@@ -1769,7 +1773,7 @@ fragment MainPSOut ps_main(
main.current_depth = ds_tex.read(coord).x;
}
if (NEEDS_RT || (PS_ROV_COLOR && any(cb.fbmask == 0xff)))
if (NEEDS_RT)
{
if (PS_ROV_COLOR)
{
@@ -1797,8 +1801,6 @@ fragment MainPSOut ps_main(
ds_rov.write(out.depth, coord);
if (PS_ROV_COLOR && !main.color_discarded)
{
if (!PS_FBMASK)
out.c0 = select(out.c0, main.current_color, cb.fbmask == 0xff);
if (ROV_NEEDS_R32)
rt_u32.write(pack_float_to_unorm4x8(out.c0), coord);
else
+7 -1
View File
@@ -432,6 +432,8 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
const char* name = shader.EntryPoint();
std::string macro;
macro += fmt::format("#define PRIMID_MAX {}\n", GSShader::PRIMID_MAX);
macro += fmt::format("#define PRIMID_MIN {}\n", GSShader::PRIMID_MIN);
macro += fmt::format("#define HAS_BILN {}\n", static_cast<int>(shader.Biln()));
macro += fmt::format("#define HAS_STENCIL_OUTPUT {}\n", static_cast<int>(shader.StencilOutput()));
macro += fmt::format("#define HAS_INTEGER_OUTPUT {}\n", static_cast<int>(shader.IntegerOutputBpp() != 0));
@@ -609,9 +611,13 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
for (size_t i = 0; i < std::size(m_date.primid_ps); i++)
{
std::string macro;
macro += fmt::format("#define PRIMID_MAX {}\n", GSShader::PRIMID_MAX);
macro += fmt::format("#define PRIMID_MIN {}\n", GSShader::PRIMID_MIN);
const std::string ps(GetShaderSource(
fmt::format("ps_primid_image_init_{}", i),
GL_FRAGMENT_SHADER, *convert_glsl));
GL_FRAGMENT_SHADER, *convert_glsl, macro));
m_shader_cache.GetProgram(&m_date.primid_ps[i], m_convert.vs, ps);
m_date.primid_ps[i].SetFormattedName("PrimID Destination Alpha Init %d", i);
}
+18 -136
View File
@@ -287,125 +287,6 @@ void ConvertVertexBuffer(const GSDrawingContext* RESTRICT ctx, GSVertexSW* RESTR
}
}
// Fix ST coordinates that would overflow the rasterizer fixed point format by rewriting the vertices.
template <u32 primclass>
void GSRendererSW::RewriteVerticesIfSTOverflow()
{
if (PRIM->TME && PRIM->FST == 0)
{
const GSVector4 tsize = GSVector4(
static_cast<float>(1 << m_context->TEX0.TW),
static_cast<float>(1 << m_context->TEX0.TH),
1.0f,
1.0f);
// SW rasterizer stores UV in 1.15.16 format so clamp to +/- (2^15 - 2) (-2 so bilinear doesn't overflow).
// Do the division by texture size here to avoid divisions for each vertex.
const GSVector4 OVERFLOW_VAL = GSVector4::cxpr(static_cast<float>((1 << 15) - 2)) / tsize;
// Only rewrite big/small S or T when the clamping mode is CLAMP or REGION_CLAMP.
const GSVector4i clamp_mode = GSVector4i(
(m_context->CLAMP.WMS == CLAMP_CLAMP || m_context->CLAMP.WMS == CLAMP_REGION_CLAMP) ? 0xFFFFFFFF : 0,
(m_context->CLAMP.WMT == CLAMP_CLAMP || m_context->CLAMP.WMT == CLAMP_REGION_CLAMP) ? 0xFFFFFFFF : 0,
0,
0);
const bool st_overflow =
((GSVector4i::cast(m_vt.m_min.t <= -OVERFLOW_VAL * tsize) & clamp_mode).mask() & 3) ||
((GSVector4i::cast(m_vt.m_max.t >= OVERFLOW_VAL * tsize) & clamp_mode).mask() & 3) ||
m_vt.nan.value;
if (st_overflow)
{
constexpr int n = GSUtil::GetClassVertexCount(primclass);
// Make sure the copy buffer is large enough.
while (m_max_vertex_count < m_index->tail)
GrowVertexBuffer();
GSVertex* RESTRICT vertex = m_vertex->buff;
GSVertex* RESTRICT vertex_copy = m_vertex->buff_copy;
u16* RESTRICT index = m_index->buff;
for (int i = 0; i < static_cast<int>(m_index->tail); i += n)
{
GSVector4 stcq[n];
// Load STQ for this primitive.
for (int j = 0; j < n; j++)
stcq[j] = GSVector4::cast(GSVector4i(vertex[index[i + j]].m[0]));
// Perform Q division and see which values need to be rewritten.
GSVector4 uv[n];
GSVector4i small{}, big{}, nan{};
for (int j = 0; j < n; j++)
{
// For sprites always use Q of second vertex.
const GSVector4 q = primclass == GS_SPRITE_CLASS ? stcq[1].wwww() : stcq[j].wwww();
uv[j] = (stcq[j] / q).xyzw(GSVector4::zero());
small |= GSVector4i::cast(uv[j] <= -OVERFLOW_VAL);
big |= GSVector4i::cast(uv[j] >= OVERFLOW_VAL);
nan |= GSVector4i::cast(uv[j] != uv[j]);
}
// Get the new values for fields that will be rewritten.
// The follows rules are used:
// 1. If there are small values but not big or nans, make all vertices small.
// 2. If there are big values but not small or nans, make all vertices big.
// 3. If there are both big and small values, or nans, make all vertices zero.
GSVector4 uv_new = GSVector4::zero();
uv_new = uv_new.blend32(-OVERFLOW_VAL, GSVector4::cast(small));
uv_new = uv_new.blend32(OVERFLOW_VAL, GSVector4::cast(big));
uv_new = uv_new.blend32(GSVector4::zero(), GSVector4::cast((small & big) | nan));
const GSVector4i rewrite = (((small | big) & clamp_mode) | nan).upl64(GSVector4i::zero());
// If both S and T are rewritten, no point in keeping Q. Just set it to 1.0f;
if ((GSVector4::cast(rewrite).mask() & 3) == 3)
{
for (int j = 0; j < n; j++)
stcq[j] = stcq[j].template insert32<0, 3>(GSVector4::m_one);
}
// Rewrite the fields that require it and write to the copy buffer.
for (int j = 0; j < n; j++)
{
// For sprites always use Q of second vertex.
const GSVector4 q = (primclass == GS_SPRITE_CLASS) ? stcq[1].wwww() : stcq[j].wwww();
stcq[j] = stcq[j].blend32(uv_new * q, GSVector4::cast(rewrite));
vertex_copy[i + j].m[0] = GSVector4i::cast(stcq[j]);
vertex_copy[i + j].m[1] = vertex[index[i + j]].m[1];
index[i + j] = i + j;
}
}
// Swap the buffers and fix the counts.
std::swap(m_vertex->buff, m_vertex->buff_copy);
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
// Recalculate ST min/max/eq in the vertex trace.
GSVector4 tmin = GSVector4::cxpr(FLT_MAX);
GSVector4 tmax = GSVector4::cxpr(-FLT_MAX);
for (int i = 0; i < static_cast<int>(m_index->tail); i += n)
{
for (int j = 0; j < n; j++)
{
GSVector4 stcq = GSVector4::cast(GSVector4i(m_vertex->buff[i + j].m[0]));
const float Q = (primclass == GS_SPRITE_CLASS) ? stcq.w : m_vertex->buff[i + 1].RGBAQ.Q;
stcq = (stcq / Q).xyzw(stcq);
tmin = tmin.min(stcq);
tmax = tmax.max(stcq);
}
}
m_vt.m_min.t = tmin.xyww() * tsize;
m_vt.m_max.t = tmax.xyww() * tsize;
m_vt.m_eq.stq = (m_vt.m_min.t == m_vt.m_max.t).mask();
}
}
}
void GSVertexSWInitStatic()
{
#define InitCVB4(P, T, F, Q) GSVertexSW::s_cvb[P][T][F][Q] = ConvertVertexBuffer<P, T, F, Q>;
@@ -427,24 +308,25 @@ MULTI_ISA_UNSHARED_END
void GSRendererSW::Draw()
{
const GSDrawingContext* context = m_context;
switch (m_vt.m_primclass)
if (!GSConfig.UserHacks_RewriteLargeST)
{
case GS_POINT_CLASS:
RewriteVerticesIfSTOverflow<GS_POINT_CLASS>();
break;
case GS_LINE_CLASS:
RewriteVerticesIfSTOverflow<GS_LINE_CLASS>();
break;
case GS_TRIANGLE_CLASS:
RewriteVerticesIfSTOverflow<GS_TRIANGLE_CLASS>();
break;
case GS_SPRITE_CLASS:
RewriteVerticesIfSTOverflow<GS_SPRITE_CLASS>();
break;
default:
pxFailRel("Unknown primitive class.");
break;
// SW rasterizer stores UV in 1.15.16 format so clamp to +/- (2^15 - 2) (-2 so bilinear doesn't overflow).
// Also only do this for CLAMP and CLAMP_REGION modes to reduce the performance impact.
const GSVector4 tsize = GSVector4(
static_cast<float>(1 << m_context->TEX0.TW),
static_cast<float>(1 << m_context->TEX0.TH),
1.0f,
1.0f);
// Large value is given in terms of normalized ST / Q coordinates.
const GSVector4 large_val = GSVector4::cxpr(static_cast<float>((1 << 15) - 2)) / tsize;
RewriteVerticesIfLargeST(large_val, true);
}
else
{
// More sensitive as the rewrite threshold is lower and we do it for all clamp mode.
// The performance cost will also be higher.
RewriteVerticesIfLargeST(GSVector4::cxpr(16.0f), false);
}
auto data = m_vertex_heap.make_shared<SharedData>().cast<GSRasterizerData>();
+9 -2
View File
@@ -4200,6 +4200,8 @@ bool GSDeviceVK::CompileConvertPipelines()
gpb.SetColorWriteMask(0, shader.Mask());
std::string macro;
macro += fmt::format("#define PRIMID_MAX {}\n", GSShader::PRIMID_MAX);
macro += fmt::format("#define PRIMID_MIN {}\n", GSShader::PRIMID_MIN);
macro += fmt::format("#define HAS_BILN {}\n", static_cast<int>(shader.Biln()));
macro += fmt::format("#define HAS_STENCIL_OUTPUT {}\n", static_cast<int>(shader.StencilOutput()));
macro += fmt::format("#define HAS_INTEGER_OUTPUT {}\n", static_cast<int>(shader.IntegerOutputBpp() != 0));
@@ -4267,9 +4269,14 @@ bool GSDeviceVK::CompileConvertPipelines()
for (u32 datm = 0; datm < 4; datm++)
{
std::string macro;
macro += fmt::format("#define PRIMID_MAX {}\n", GSShader::PRIMID_MAX);
macro += fmt::format("#define PRIMID_MIN {}\n", GSShader::PRIMID_MIN);
const std::string source_with_header = macro + *source;
const std::string entry_point(StringUtil::StdStringFromFormat("ps_primid_image_init_%d", datm));
VkShaderModule ps =
GetUtilityFragmentShader(*source, entry_point.c_str());
VkShaderModule ps = GetUtilityFragmentShader(source_with_header, entry_point.c_str());
if (ps == VK_NULL_HANDLE)
return false;
+2 -2
View File
@@ -104,9 +104,9 @@ std::unique_ptr<GSTextureVK> GSTextureVK::Create(Usage usage, Format format, int
if (format == Format::UNorm8)
{
// for r8 textures, swizzle it across all 4 components. the shaders depend on it being in alpha.. why?
// UNorm8 wants value in alpha, not red.
static constexpr const VkComponentMapping r8_swizzle = {
VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R };
VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_R };
vci.components = r8_swizzle;
}
+8
View File
@@ -376,6 +376,7 @@ static const char* s_gs_hw_fix_names[] = {
"nativePaletteDraw",
"estimateTextureRegion",
"drawBuffering",
"rewriteLargeST",
"PCRTCOffsets",
"PCRTCOverscan",
"trilinearFiltering",
@@ -622,6 +623,9 @@ bool GameDatabaseSchema::GameEntry::configMatchesHWFix(const Pcsx2Config::GSOpti
case GSHWFixId::DrawBuffering:
return (static_cast<int>(config.UserHacks_DrawBuffering) == value);
case GSHWFixId::RewriteLargeST:
return (static_cast<int>(config.UserHacks_RewriteLargeST) == value);
case GSHWFixId::PCRTCOffsets:
return (static_cast<int>(config.PCRTCOffsets) == value);
@@ -793,6 +797,10 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions&
config.UserHacks_DrawBuffering = (value > 0);
break;
case GSHWFixId::RewriteLargeST:
config.UserHacks_RewriteLargeST = (value > 0);
break;
case GSHWFixId::PCRTCOffsets:
config.PCRTCOffsets = (value > 0);
break;
+1
View File
@@ -61,6 +61,7 @@ namespace GameDatabaseSchema
NativePaletteDraw,
EstimateTextureRegion,
DrawBuffering,
RewriteLargeST,
PCRTCOffsets,
PCRTCOverscan,
+1 -1
View File
@@ -267,7 +267,7 @@ bool GameList::GetIsoSerialAndCRC(const std::string& path, s32* disc_type, std::
// TODO: we could include the version in the game list?
*disc_type = DoCDVDdetectDiskType();
cdvdGetDiscInfo(serial, nullptr, nullptr, crc, nullptr);
cdvdGetDiscInfo(serial, nullptr, nullptr, nullptr, crc, nullptr);
DoCDVDclose();
return true;
}
+5
View File
@@ -3236,6 +3236,9 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_CROP, "Estimate Texture Region"),
FSUI_CSTR("Attempts to reduce the texture size when games do not set it themselves (e.g. Snowblind games)."), "EmuCore/GS",
"UserHacks_EstimateTextureRegion", false, manual_hw_fixes);
DrawToggleSetting(bsi, FSUI_CSTR("Rewrite Large ST"),
FSUI_CSTR("Rewrite large ST coordinates and clamp the values."), "EmuCore/GS",
"UserHacks_RewriteLargeST", false, manual_hw_fixes);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_PALETTE, "GPU Palette Conversion"),
FSUI_CSTR("When enabled GPU converts colormap-textures, otherwise the CPU will. It is a trade-off between GPU and CPU."),
"EmuCore/GS", "paltex", false, manual_hw_fixes);
@@ -6014,6 +6017,8 @@ TRANSLATE_NOOP("FullscreenUI", "Removes texture cache entries when there is any
TRANSLATE_NOOP("FullscreenUI", "Allows the texture cache to reuse as an input texture the inner portion of a previous framebuffer.");
TRANSLATE_NOOP("FullscreenUI", "Flushes all targets in the texture cache back to local memory when shutting down.");
TRANSLATE_NOOP("FullscreenUI", "Attempts to reduce the texture size when games do not set it themselves (e.g. Snowblind games).");
TRANSLATE_NOOP("FullscreenUI", "Rewrite Large ST");
TRANSLATE_NOOP("FullscreenUI", "Rewrite large ST coordinates and clamp the values.");
TRANSLATE_NOOP("FullscreenUI", "When enabled GPU converts colormap-textures, otherwise the CPU will. It is a trade-off between GPU and CPU.");
TRANSLATE_NOOP("FullscreenUI", "Attempts to reduce draw calls in games which do heavy context switching for blending purposes.");
TRANSLATE_NOOP("FullscreenUI", "Truncate 32-bit depth values to 24 bits. Helps games struggling with Z-fighting.");
+2
View File
@@ -1021,6 +1021,8 @@ __ri void ImGuiManager::DrawSettingsOverlay(float scale, float margin, float spa
APPEND("ETR ");
if (GSConfig.UserHacks_DrawBuffering)
APPEND("DRWB ");
if (GSConfig.UserHacks_RewriteLargeST)
APPEND("RWST ");
if (GSConfig.HWSpinGPUForReadbacks)
APPEND("RBSG ");
if (GSConfig.HWSpinCPUForReadbacks)
+2
View File
@@ -1005,6 +1005,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapIntEnumEx(UserHacks_Limit24BitDepth, "UserHacks_Limit24BitDepth");
SettingsWrapBitBoolEx(UserHacks_EstimateTextureRegion, "UserHacks_EstimateTextureRegion");
SettingsWrapBitBoolEx(UserHacks_DrawBuffering, "UserHacks_DrawBuffering");
SettingsWrapBitBoolEx(UserHacks_RewriteLargeST, "UserHacks_RewriteLargeST");
SettingsWrapBitBoolEx(FXAA, "fxaa");
SettingsWrapBitBool(ShadeBoost);
SettingsWrapBitBoolEx(DumpGSData, "DumpGSData");
@@ -1145,6 +1146,7 @@ void Pcsx2Config::GSOptions::MaskUserHacks()
UserHacks_Limit24BitDepth = GSLimit24BitDepth::Disabled;
UserHacks_EstimateTextureRegion = false;
UserHacks_DrawBuffering = false;
UserHacks_RewriteLargeST = false;
UserHacks_TCOffsetX = 0;
UserHacks_TCOffsetY = 0;
UserHacks_CPUSpriteRenderBW = 0;
+1 -1
View File
@@ -679,7 +679,7 @@ void eeloadHook()
{
CDVDDiscType disc_type;
std::string disc_elf;
cdvdGetDiscInfo(nullptr, &disc_elf, nullptr, nullptr, &disc_type);
cdvdGetDiscInfo(nullptr, &disc_elf, nullptr, nullptr, nullptr, &disc_type);
if (disc_type == CDVDDiscType::PS2Disc)
{
// only allow fast boot for PS2 games
+2 -3
View File
@@ -23,8 +23,6 @@ bool SaveStateBase::InputRecordingFreeze()
#include "common/FileSystem.h"
#include "common/StringUtil.h"
#include "Counters.h"
#include "SaveState.h"
#include "VMManager.h"
#include "Host.h"
#include "ImGui/ImGuiOverlays.h"
@@ -32,7 +30,7 @@ bool SaveStateBase::InputRecordingFreeze()
#include "GameDatabase.h"
#include "fmt/format.h"
#include "GS.h"
#include "Host.h"
#include "SIO/Pad/Pad.h"
InputRecording g_InputRecording;
@@ -139,6 +137,7 @@ void InputRecording::closeActiveFile()
if (m_file.close())
{
m_is_active = false;
Pad::ResetAllControllerInputs();
InputRec::log(TRANSLATE_STR("InputRecording", "Input recording stopped"), Host::OSD_ERROR_DURATION);
MTGS::PresentCurrentFrame();
}
@@ -7,6 +7,7 @@
#include "InputRecording.h"
#include "InputRecordingControls.h"
#include "Utilities/InputRecordingLogger.h"
#include "SIO/Pad/Pad.h"
#include "Host.h"
#include "MTGS.h"
@@ -29,6 +30,7 @@ void InputRecordingControls::setRecordMode(bool waitForFrameToEnd)
if (!waitForFrameToEnd || VMManager::GetState() == VMState::Paused)
{
m_state = Mode::Recording;
Pad::ResetAllControllerInputs();
InputRec::log(TRANSLATE("InputRecordingControls","Record Mode Enabled"), Host::OSD_INFO_DURATION);
MTGS::PresentCurrentFrame();
}
@@ -36,6 +38,7 @@ void InputRecordingControls::setRecordMode(bool waitForFrameToEnd)
{
m_controlQueue.push([&]() {
m_state = Mode::Recording;
Pad::ResetAllControllerInputs();
InputRec::log(TRANSLATE("InputRecordingControls","Record Mode Enabled"), Host::OSD_INFO_DURATION);
});
}
+26
View File
@@ -554,6 +554,32 @@ void Pad::SetControllerState(u32 controller, u32 bind, float value)
s_controllers[controller]->Set(bind, value);
}
void Pad::ResetControllerInputs(u32 controller)
{
if (controller >= NUM_CONTROLLER_PORTS || !HasConnectedPad(controller))
return;
for (InputBindingInfo binding : s_controllers[controller]->GetInfo().bindings)
{
switch (binding.bind_type)
{
case InputBindingInfo::Type::Button:
case InputBindingInfo::Type::Axis:
case InputBindingInfo::Type::HalfAxis:
s_controllers[controller]->Set(binding.bind_index, 0);
break;
default:
break;
}
}
}
void Pad::ResetAllControllerInputs()
{
for (u32 port = 0; port < NUM_CONTROLLER_PORTS; port++)
ResetControllerInputs(port);
}
bool Pad::Freeze(StateWrapper& sw)
{
if (sw.IsReading())
+4
View File
@@ -66,6 +66,10 @@ namespace Pad
// Sets the specified bind on a controller to the specified pressure (normalized to 0..1).
void SetControllerState(u32 controller, u32 bind, float value);
// Resets all input binds on a controller to their default state.
void ResetControllerInputs(u32 controller);
void ResetAllControllerInputs();
bool Freeze(StateWrapper& sw);
// Sets the state of the specified macro button.
+1 -1
View File
@@ -3,4 +3,4 @@
/// Version number for GS and other shaders. Increment whenever any of the contents of the
/// shaders change, to invalidate the cache.
static constexpr u32 SHADER_CACHE_VERSION = 110; // Last changed in PR 14897
static constexpr u32 SHADER_CACHE_VERSION = 113; // Last changed in PR 14743
+15 -1
View File
@@ -171,6 +171,7 @@ static std::string s_disc_version;
static std::string s_title;
static std::string s_title_en_search;
static std::string s_title_en_replace;
static std::string s_cur_region;
static u32 s_disc_crc;
static u32 s_current_crc;
static u32 s_elf_entry_point = 0xFFFFFFFFu;
@@ -1051,11 +1052,12 @@ void VMManager::UpdateDiscDetails(bool booting)
s_disc_crc = GSDumpReplayer::GetDumpCRC();
s_disc_elf = {};
s_disc_version = {};
s_cur_region = "NTSC";
serial_is_valid = !s_disc_serial.empty();
}
else if (CDVDsys_GetSourceType() != CDVD_SourceType::NoDisc)
{
cdvdGetDiscInfo(&s_disc_serial, &s_disc_elf, &s_disc_version, &s_disc_crc, nullptr);
cdvdGetDiscInfo(&s_disc_serial, &s_disc_elf, &s_disc_version, &s_cur_region, &s_disc_crc, nullptr);
serial_is_valid = !s_disc_serial.empty();
}
else if (!s_elf_override.empty())
@@ -1063,12 +1065,14 @@ void VMManager::UpdateDiscDetails(bool booting)
s_disc_serial = Path::GetFileTitle(s_elf_override);
s_disc_version = {};
s_disc_crc = 0; // set below
s_cur_region = "NTSC";
}
else
{
s_disc_serial = BiosSerial;
s_disc_version = {};
s_disc_crc = 0;
s_cur_region = (BiosZone == "Europe") ? "PAL" : "NTSC";
title = fmt::format(TRANSLATE_FS("VMManager", "PS2 BIOS ({})"), BiosZone);
}
@@ -2818,6 +2822,11 @@ bool VMManager::Internal::IsFastBootInProgress()
return s_fast_boot_requested && !HasBootedELF();
}
std::string VMManager::Internal::GetCurrentRegion()
{
return s_cur_region;
}
void VMManager::Internal::DisableFastBoot()
{
if (!s_fast_boot_requested)
@@ -3270,6 +3279,11 @@ void VMManager::WarnAboutUnsafeSettings()
append(ICON_FA_CIRCLE_EXCLAMATION,
TRANSLATE_SV("VMManager", "Draw Buffering is enabled, this may result in graphical errors."));
}
if (EmuConfig.GS.UserHacks_RewriteLargeST)
{
append(ICON_FA_CIRCLE_EXCLAMATION,
TRANSLATE_SV("VMManager", "Rewrite large ST is enabled, this may reduce performance."));
}
if (EmuConfig.GS.DumpReplaceableTextures)
{
append(ICON_FA_CIRCLE_EXCLAMATION,
+3
View File
@@ -306,6 +306,9 @@ namespace VMManager
/// Returns true if fast booting is active (requested but ELF not started).
bool IsFastBootInProgress();
// Returns the current disc/BIOS region, if set.
std::string GetCurrentRegion();
/// Disables fast boot if it was requested, and found to be incompatible.
void DisableFastBoot();