Skip to content

Commit

Permalink
Merge pull request #18646 from hrydgard/through-mode-smart-filtering
Browse files Browse the repository at this point in the history
Through-mode smart texture filtering
  • Loading branch information
hrydgard committed Jan 12, 2024
2 parents 6993cc9 + f2ea9f0 commit 971edc6
Show file tree
Hide file tree
Showing 61 changed files with 207 additions and 19 deletions.
1 change: 1 addition & 0 deletions Common/GPU/Vulkan/VulkanFrameData.h
Expand Up @@ -29,6 +29,7 @@ struct QueueProfileContext {
double cpuEndTime;
double descWriteTime;
int descriptorsWritten;
int descriptorsDeduped;
#ifdef _DEBUG
int commandCounts[11];
#endif
Expand Down
7 changes: 5 additions & 2 deletions Common/GPU/Vulkan/VulkanRenderManager.cpp
Expand Up @@ -687,7 +687,7 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
descUpdateTimeMs_.Update(frameData.profile.descWriteTime * 1000.0);
descUpdateTimeMs_.Format(line, sizeof(line));
str << line;
snprintf(line, sizeof(line), "Descriptors written: %d\n", frameData.profile.descriptorsWritten);
snprintf(line, sizeof(line), "Descriptors written: %d (dedup: %d)\n", frameData.profile.descriptorsWritten, frameData.profile.descriptorsDeduped);
str << line;
snprintf(line, sizeof(line), "Resource deletions: %d\n", vulkan_->GetLastDeleteCount());
str << line;
Expand Down Expand Up @@ -737,6 +737,7 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
}

frameData.profile.descriptorsWritten = 0;
frameData.profile.descriptorsDeduped = 0;

// Must be after the fence - this performs deletes.
VLOG("PUSH: BeginFrame %d", curFrame);
Expand Down Expand Up @@ -1747,7 +1748,7 @@ void VKRPipelineLayout::FlushDescSets(VulkanContext *vulkan, int frame, QueuePro
VkDescriptorBufferInfo bufferInfo[MAX_DESC_SET_BINDINGS];

size_t start = data.flushedDescriptors_;
int writeCount = 0;
int writeCount = 0, dedupCount = 0;

for (size_t index = start; index < descSets.size(); index++) {
auto &d = descSets[index];
Expand All @@ -1759,6 +1760,7 @@ void VKRPipelineLayout::FlushDescSets(VulkanContext *vulkan, int frame, QueuePro
if (descSets[index - 1].count == d.count) {
if (!memcmp(descData.data() + d.offset, descData.data() + descSets[index - 1].offset, d.count * sizeof(PackedDescriptor))) {
d.set = descSets[index - 1].set;
dedupCount++;
continue;
}
}
Expand Down Expand Up @@ -1841,4 +1843,5 @@ void VKRPipelineLayout::FlushDescSets(VulkanContext *vulkan, int frame, QueuePro

data.flushedDescriptors_ = (int)descSets.size();
profile->descriptorsWritten += writeCount;
profile->descriptorsDeduped += dedupCount;
}
1 change: 1 addition & 0 deletions Core/Config.cpp
Expand Up @@ -611,6 +611,7 @@ static const ConfigSetting graphicsSettings[] = {
ConfigSetting("HardwareTransform", &g_Config.bHardwareTransform, true, CfgFlag::PER_GAME | CfgFlag::REPORT),
ConfigSetting("SoftwareSkinning", &g_Config.bSoftwareSkinning, true, CfgFlag::PER_GAME | CfgFlag::REPORT),
ConfigSetting("TextureFiltering", &g_Config.iTexFiltering, 1, CfgFlag::PER_GAME | CfgFlag::REPORT),
ConfigSetting("Smart2DTexFiltering", &g_Config.bSmart2DTexFiltering, false, CfgFlag::PER_GAME | CfgFlag::REPORT),
ConfigSetting("InternalResolution", &g_Config.iInternalResolution, &DefaultInternalResolution, CfgFlag::PER_GAME | CfgFlag::REPORT),
ConfigSetting("AndroidHwScale", &g_Config.iAndroidHwScale, &DefaultAndroidHwScale, CfgFlag::DEFAULT),
ConfigSetting("HighQualityDepth", &g_Config.bHighQualityDepth, true, CfgFlag::PER_GAME | CfgFlag::REPORT),
Expand Down
1 change: 1 addition & 0 deletions Core/Config.h
Expand Up @@ -172,6 +172,7 @@ struct Config {
bool bDisableRangeCulling;

int iTexFiltering; // 1 = auto , 2 = nearest , 3 = linear , 4 = auto max quality
bool bSmart2DTexFiltering;

bool bDisplayStretch; // Automatically matches the aspect ratio of the window.
int iDisplayFilter; // 1 = linear, 2 = nearest
Expand Down
78 changes: 71 additions & 7 deletions GPU/Common/SoftwareTransformCommon.cpp
Expand Up @@ -179,7 +179,8 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de

float uscale = 1.0f;
float vscale = 1.0f;
if (throughmode) {
if (throughmode && prim != GE_PRIM_RECTANGLES) {
// For through rectangles, we do this scaling in Expand.
uscale /= gstate_c.curTextureWidth;
vscale /= gstate_c.curTextureHeight;
}
Expand Down Expand Up @@ -496,7 +497,7 @@ void SoftwareTransform::BuildDrawingParams(int prim, int vertexCount, u32 vertTy
bool useBufferedRendering = fbman->UseBufferedRendering();

if (prim == GE_PRIM_RECTANGLES) {
ExpandRectangles(vertexCount, maxIndex, inds, transformed, transformedExpanded, numTrans, throughmode);
ExpandRectangles(vertexCount, maxIndex, inds, transformed, transformedExpanded, numTrans, throughmode, &result->pixelMapped);
result->drawBuffer = transformedExpanded;
result->drawIndexed = true;

Expand All @@ -517,14 +518,17 @@ void SoftwareTransform::BuildDrawingParams(int prim, int vertexCount, u32 vertTy
ExpandPoints(vertexCount, maxIndex, inds, transformed, transformedExpanded, numTrans, throughmode);
result->drawBuffer = transformedExpanded;
result->drawIndexed = true;
result->pixelMapped = false;
} else if (prim == GE_PRIM_LINES) {
ExpandLines(vertexCount, maxIndex, inds, transformed, transformedExpanded, numTrans, throughmode);
result->drawBuffer = transformedExpanded;
result->drawIndexed = true;
result->pixelMapped = false;
} else {
// We can simply draw the unexpanded buffer.
numTrans = vertexCount;
result->drawIndexed = true;
result->pixelMapped = false;

// If we don't support custom cull in the shader, process it here.
if (!gstate_c.Use(GPU_USE_CULL_DISTANCE) && vertexCount > 0 && !throughmode) {
Expand Down Expand Up @@ -581,6 +585,37 @@ void SoftwareTransform::BuildDrawingParams(int prim, int vertexCount, u32 vertTy

inds = newInds;
}
} else if (throughmode && g_Config.bSmart2DTexFiltering) {
// We check some common cases for pixel mapping.
// TODO: It's not really optimal that some previous step has removed the triangle strip.
if (vertexCount <= 6 && prim == GE_PRIM_TRIANGLES) {
// It's enough to check UV deltas vs pos deltas between vertex pairs:
// 0-1 1-3 3-2 2-0. Maybe can even skip the last one. Probably some simple math can get us that sequence.
// Unfortunately we need to reverse the previous UV scaling operation. Fortunately these are powers of two
// so the operations are exact.
bool pixelMapped = true;
const u16 *indsIn = (const u16 *)inds;
for (int t = 0; t < vertexCount; t += 3) {
float uscale = gstate_c.curTextureWidth;
float vscale = gstate_c.curTextureHeight;
struct { int a; int b; } pairs[] = { {0, 1}, {1, 2}, {2, 0} };
for (int i = 0; i < ARRAY_SIZE(pairs); i++) {
int a = indsIn[t + pairs[i].a];
int b = indsIn[t + pairs[i].b];
float du = fabsf((transformed[a].u - transformed[b].u) * uscale);
float dv = fabsf((transformed[a].v - transformed[b].v) * vscale);
float dx = fabsf(transformed[a].x - transformed[b].x);
float dy = fabsf(transformed[a].y - transformed[b].y);
if (du != dx || dv != dy) {
pixelMapped = false;
}
}
if (!pixelMapped) {
break;
}
}
result->pixelMapped = pixelMapped;
}
}
}

Expand Down Expand Up @@ -610,7 +645,7 @@ void SoftwareTransform::CalcCullParams(float &minZValue, float &maxZValue) {
std::swap(minZValue, maxZValue);
}

void SoftwareTransform::ExpandRectangles(int vertexCount, int &maxIndex, u16 *&inds, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode) {
void SoftwareTransform::ExpandRectangles(int vertexCount, int &maxIndex, u16 *&inds, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode, bool *pixelMappedExactly) {
// Rectangles always need 2 vertices, disregard the last one if there's an odd number.
vertexCount = vertexCount & ~1;
numTrans = 0;
Expand All @@ -621,32 +656,59 @@ void SoftwareTransform::ExpandRectangles(int vertexCount, int &maxIndex, u16 *&i
u16 *indsOut = newInds;

maxIndex = 4 * (vertexCount / 2);

float uscale = 1.0f;
float vscale = 1.0f;
if (throughmode) {
uscale /= gstate_c.curTextureWidth;
vscale /= gstate_c.curTextureHeight;
}

bool pixelMapped = g_Config.bSmart2DTexFiltering;

for (int i = 0; i < vertexCount; i += 2) {
const TransformedVertex &transVtxTL = transformed[indsIn[i + 0]];
const TransformedVertex &transVtxBR = transformed[indsIn[i + 1]];

if (pixelMapped) {
float dx = transVtxBR.x - transVtxTL.x;
float dy = transVtxBR.y - transVtxTL.y;
float du = transVtxBR.u - transVtxTL.u;
float dv = transVtxBR.v - transVtxTL.v;

// NOTE: We will accept it as pixel mapped if only one dimension is stretched. This fixes dialog frames in FFI.
// Though, there could be false positives in other games due to this. Let's see if it is a problem...
if (dx <= 0 || dy <= 0 || (dx != du && dy != dv)) {
pixelMapped = false;
}
}

// We have to turn the rectangle into two triangles, so 6 points.
// This is 4 verts + 6 indices.

// bottom right
trans[0] = transVtxBR;
trans[0].u = transVtxBR.u * uscale;
trans[0].v = transVtxBR.v * vscale;

// top right
trans[1] = transVtxBR;
trans[1].y = transVtxTL.y;
trans[1].v = transVtxTL.v;
trans[1].u = transVtxBR.u * uscale;
trans[1].v = transVtxTL.v * vscale;

// top left
trans[2] = transVtxBR;
trans[2].x = transVtxTL.x;
trans[2].y = transVtxTL.y;
trans[2].u = transVtxTL.u;
trans[2].v = transVtxTL.v;
trans[2].u = transVtxTL.u * uscale;
trans[2].v = transVtxTL.v * vscale;

// bottom left
trans[3] = transVtxBR;
trans[3].x = transVtxTL.x;
trans[3].u = transVtxTL.u;
trans[3].u = transVtxTL.u * uscale;
trans[3].v = transVtxBR.v * vscale;

// That's the four corners. Now process UV rotation.
if (throughmode) {
Expand All @@ -663,12 +725,14 @@ void SoftwareTransform::ExpandRectangles(int vertexCount, int &maxIndex, u16 *&i
indsOut[3] = i * 2 + 3;
indsOut[4] = i * 2 + 0;
indsOut[5] = i * 2 + 2;

trans += 4;
indsOut += 6;

numTrans += 6;
}
inds = newInds;
*pixelMappedExactly = pixelMapped;
}

void SoftwareTransform::ExpandLines(int vertexCount, int &maxIndex, u16 *&inds, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode) {
Expand Down
4 changes: 3 additions & 1 deletion GPU/Common/SoftwareTransformCommon.h
Expand Up @@ -45,6 +45,8 @@ struct SoftwareTransformResult {
TransformedVertex *drawBuffer;
int drawNumTrans;
bool drawIndexed;

bool pixelMapped;
};

struct SoftwareTransformParams {
Expand All @@ -70,7 +72,7 @@ class SoftwareTransform {

protected:
void CalcCullParams(float &minZValue, float &maxZValue);
void ExpandRectangles(int vertexCount, int &maxIndex, u16 *&inds, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode);
void ExpandRectangles(int vertexCount, int &maxIndex, u16 *&inds, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode, bool *pixelMappedExactly);
void ExpandLines(int vertexCount, int &maxIndex, u16 *&inds, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode);
void ExpandPoints(int vertexCount, int &maxIndex, u16 *&inds, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode);

Expand Down
6 changes: 6 additions & 0 deletions GPU/Common/TextureCacheCommon.cpp
Expand Up @@ -261,6 +261,9 @@ SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, const TexCac
if (uglyColorTest)
forceFiltering = TEX_FILTER_FORCE_NEAREST;
}
if (gstate_c.pixelMapped) {
forceFiltering = TEX_FILTER_FORCE_NEAREST;
}
break;
case TEX_FILTER_FORCE_LINEAR:
// Override to linear filtering if there's no alpha or color testing going on.
Expand All @@ -281,6 +284,9 @@ SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, const TexCac
if (uglyColorTest)
forceFiltering = TEX_FILTER_FORCE_NEAREST;
}
if (gstate_c.pixelMapped) {
forceFiltering = TEX_FILTER_FORCE_NEAREST;
}
break;
}
}
Expand Down
5 changes: 4 additions & 1 deletion GPU/D3D11/DrawEngineD3D11.cpp
Expand Up @@ -408,8 +408,11 @@ void DrawEngineD3D11::DoFlush() {
if (result.action == SW_CLEAR && everUsedEqualDepth_ && gstate.isClearModeDepthMask() && result.depth > 0.0f && result.depth < 1.0f)
result.action = SW_NOT_READY;

if (textureNeedsApply)
if (textureNeedsApply) {
gstate_c.pixelMapped = result.pixelMapped;
textureCache_->ApplyTexture();
gstate_c.pixelMapped = false;
}

// Need to ApplyDrawState after ApplyTexture because depal can launch a render pass and that wrecks the state.
ApplyDrawState(prim);
Expand Down
5 changes: 4 additions & 1 deletion GPU/Directx9/DrawEngineDX9.cpp
Expand Up @@ -365,8 +365,11 @@ void DrawEngineDX9::DoFlush() {
if (result.action == SW_CLEAR && everUsedEqualDepth_ && gstate.isClearModeDepthMask() && result.depth > 0.0f && result.depth < 1.0f)
result.action = SW_NOT_READY;

if (textureNeedsApply)
if (textureNeedsApply) {
gstate_c.pixelMapped = result.pixelMapped;
textureCache_->ApplyTexture();
gstate_c.pixelMapped = false;
}

ApplyDrawState(prim);

Expand Down
5 changes: 4 additions & 1 deletion GPU/GLES/DrawEngineGLES.cpp
Expand Up @@ -393,8 +393,11 @@ void DrawEngineGLES::DoFlush() {
if (result.action == SW_CLEAR && everUsedEqualDepth_ && gstate.isClearModeDepthMask() && result.depth > 0.0f && result.depth < 1.0f)
result.action = SW_NOT_READY;

if (textureNeedsApply)
if (textureNeedsApply) {
gstate_c.pixelMapped = result.pixelMapped;
textureCache_->ApplyTexture();
gstate_c.pixelMapped = false;
}

// Need to ApplyDrawState after ApplyTexture because depal can launch a render pass and that wrecks the state.
ApplyDrawState(prim);
Expand Down
3 changes: 3 additions & 0 deletions GPU/GPUState.h
Expand Up @@ -689,6 +689,9 @@ struct GPUStateCache {
// We detect this case and go into a special drawing mode.
bool blueToAlpha;

// U/V is 1:1 to pixels. Can influence texture sampling.
bool pixelMapped;

// TODO: These should be accessed from the current VFB object directly.
u32 curRTWidth;
u32 curRTHeight;
Expand Down
2 changes: 2 additions & 0 deletions GPU/Vulkan/DrawEngineVulkan.cpp
Expand Up @@ -440,7 +440,9 @@ void DrawEngineVulkan::DoFlush() {
// to use a "pre-clear" render pass, for high efficiency on tilers.
if (result.action == SW_DRAW_PRIMITIVES) {
if (textureNeedsApply) {
gstate_c.pixelMapped = result.pixelMapped;
textureCache_->ApplyTexture();
gstate_c.pixelMapped = false;
textureCache_->GetVulkanHandles(imageView, sampler);
if (imageView == VK_NULL_HANDLE)
imageView = (VkImageView)draw_->GetNativeObject(gstate_c.textureIsArray ? Draw::NativeObject::NULL_IMAGEVIEW_ARRAY : Draw::NativeObject::NULL_IMAGEVIEW);
Expand Down
1 change: 1 addition & 0 deletions GPU/Vulkan/TextureCacheVulkan.cpp
Expand Up @@ -136,6 +136,7 @@ VkSampler SamplerCache::GetOrCreateSampler(const SamplerCacheKey &key) {
samp.magFilter = key.magFilt ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
samp.minFilter = key.minFilt ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
samp.mipmapMode = key.mipFilt ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST;

if (key.aniso) {
// Docs say the min of this value and the supported max are used.
samp.maxAnisotropy = 1 << g_Config.iAnisotropyLevel;
Expand Down
12 changes: 6 additions & 6 deletions Tools/langtool/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions UI/GameSettingsScreen.cpp
Expand Up @@ -553,6 +553,8 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings)
static const char *texFilters[] = { "Auto", "Nearest", "Linear", "Auto Max Quality"};
graphicsSettings->Add(new PopupMultiChoice(&g_Config.iTexFiltering, gr->T("Texture Filter"), texFilters, 1, ARRAY_SIZE(texFilters), I18NCat::GRAPHICS, screenManager()));

graphicsSettings->Add(new CheckBox(&g_Config.bSmart2DTexFiltering, gr->T("Smart 2D texture filtering")));

#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS)
bool showCardboardSettings = deviceType != DEVICE_TYPE_VR;
#else
Expand Down

0 comments on commit 971edc6

Please sign in to comment.