TextureCacheVulkan: Move the texture scaling upload shader and passes to common

So the Metal backend can use them too. No functional change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-19 12:02:22 +02:00
1 parent 0978a3e363
commit 55109b39fb
4 files changed
+115 -96

No files matched your search

+86
View File
@@ -275,3 +275,89 @@ void ClutTextureCache::Decimate() {
}
gpuStats.perFrame.numClutTextures = (int)texCache_.size();
}
const char *const textureScaleUploadShader = R"(
#version 450
#extension GL_ARB_separate_shader_objects : enable
// 8x8 is the most common compute shader workgroup size, and works great on all major
// hardware vendors. TODO: However, we should probably change to 16x16, as Qualcomm now has
// support for bigger groups...
layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
uniform layout(set = 0, binding = 0, rgba8) writeonly image2D img;
layout(std430, set = 0, binding = 1) buffer Buf {
uint data[];
} buf;
layout(push_constant) uniform Params {
int width;
int height;
} params;
// The cbuffer, if present, is self-declared for layout flexibility
#define CBUFFER_SET 0
#define CBUFFER_BINDING 4
uint readColoru(uvec2 p) {
return buf.data[p.y * params.width + p.x];
}
vec4 readColorf(uvec2 p) {
// Unpack the color (we could look it up in a CLUT here if we wanted...)
// The imageStore repack is free.
return unpackUnorm4x8(readColoru(p));
}
void writeColorf(ivec2 p, vec4 c) {
imageStore(img, p, c);
}
%s
// Note that main runs once per INPUT pixel, unlike the old model.
void main() {
uvec2 xy = gl_GlobalInvocationID.xy;
// Kill off any out-of-image threads to avoid stray writes.
// Should only happen on the tiniest mipmaps as PSP textures are power-of-2,
// and we use a 8x8 workgroup size. Probably not really necessary.
if (xy.x >= params.width || xy.y >= params.height)
return;
// applyScaling will write the upscaled pixels, using writeColorf above.
// It's expected to write a square of scale*scale pixels, at the location xy*scale.
applyScaling(xy);
}
)";
bool GetTextureScaleMultipassLayout(int scaleFactor, std::vector<TextureScaleScratchDesc> *scratchDescs, std::vector<TextureScaleStageDesc> *stageDescs) {
if (scaleFactor == 2) {
*scratchDescs = {
{ "nnedi3_vertical", 1, 2 },
{ "nnedi3_horizontal", 2, 2 },
};
*stageDescs = {
{ 0, -1, 0, 1, 1, 1, 2, false },
{ 1, 0, 1, 1, 2, 2, 2, false },
{ 2, 1, -1, 2, 2, 2, 2, false },
};
} else if (scaleFactor == 4) {
*scratchDescs = {
{ "nnedi3_vertical", 1, 2 },
{ "nnedi3_horizontal", 2, 2 },
{ "nnedi3_vertical2", 2, 4 },
{ "nnedi3_horizontal2", 4, 4 },
};
*stageDescs = {
{ 0, -1, 0, 1, 1, 1, 2, false },
{ 2, 0, 1, 1, 2, 2, 2, false },
{ 1, 1, 2, 2, 2, 2, 4, false },
{ 2, 2, 3, 2, 4, 4, 4, false },
{ 3, 3, -1, 4, 4, 4, 4, true },
};
} else {
return false;
}
return true;
}
+25
View File
@@ -77,3 +77,28 @@ private:
Draw2D *draw2D_;
std::unordered_map<u64, Draw2DPipeline *> pipelineCache_;
};
// Hardware texture upscaling, with compute shaders.
// The single pass upload shader, with a %s for the scaling shader (its applyScaling()).
extern const char *const textureScaleUploadShader;
struct TextureScaleScratchDesc {
const char *tag;
uint8_t widthScale;
uint8_t heightScale;
};
struct TextureScaleStageDesc {
uint8_t shaderIndex;
int8_t inputScratch; // -1 means the original upload buffer.
int8_t outputScratch; // -1 means the final destination image.
uint8_t srcWidthScale;
uint8_t srcHeightScale;
uint8_t dstWidthScale;
uint8_t dstHeightScale;
bool useFinalOutputSize;
};
// The scratch images and passes of a multipass scaling shader (NNEDI3). False for an unsupported scale factor.
bool GetTextureScaleMultipassLayout(int scaleFactor, std::vector<TextureScaleScratchDesc> *scratchDescs, std::vector<TextureScaleStageDesc> *stageDescs);
+2 -80
View File
@@ -55,60 +55,6 @@ using namespace PPSSPP_VK;
#define TEXCACHE_MAX_SLAB_SIZE (32 * 1024 * 1024)
#define TEXCACHE_SLAB_PRESSURE 4
const char *uploadShader = R"(
#version 450
#extension GL_ARB_separate_shader_objects : enable
// 8x8 is the most common compute shader workgroup size, and works great on all major
// hardware vendors. TODO: However, we should probably change to 16x16, as Qualcomm now has
// support for bigger groups...
layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
uniform layout(set = 0, binding = 0, rgba8) writeonly image2D img;
layout(std430, set = 0, binding = 1) buffer Buf {
uint data[];
} buf;
layout(push_constant) uniform Params {
int width;
int height;
} params;
// The cbuffer, if present, is self-declared for layout flexibility
#define CBUFFER_SET 0
#define CBUFFER_BINDING 4
uint readColoru(uvec2 p) {
return buf.data[p.y * params.width + p.x];
}
vec4 readColorf(uvec2 p) {
// Unpack the color (we could look it up in a CLUT here if we wanted...)
// The imageStore repack is free.
return unpackUnorm4x8(readColoru(p));
}
void writeColorf(ivec2 p, vec4 c) {
imageStore(img, p, c);
}
%s
// Note that main runs once per INPUT pixel, unlike the old model.
void main() {
uvec2 xy = gl_GlobalInvocationID.xy;
// Kill off any out-of-image threads to avoid stray writes.
// Should only happen on the tiniest mipmaps as PSP textures are power-of-2,
// and we use a 8x8 workgroup size. Probably not really necessary.
if (xy.x >= params.width || xy.y >= params.height)
return;
// applyScaling will write the upscaled pixels, using writeColorf above.
// It's expected to write a square of scale*scale pixels, at the location xy*scale.
applyScaling(xy);
}
)";
static int VkFormatBytesPerPixel(VkFormat format) {
switch (format) {
@@ -301,31 +247,7 @@ bool TextureCacheVulkan::CompileMultipassShader(VulkanContext *vulkan, const Tex
if (!shaderInfo.computeShaderFiles.empty()) {
shaderFiles = shaderInfo.computeShaderFiles;
}
if (shaderInfo.scaleFactor == 2) {
multipassScratchDescs_ = {
{ "nnedi3_vertical", 1, 2 },
{ "nnedi3_horizontal", 2, 2 },
};
multipassStageDescs_ = {
{ 0, -1, 0, 1, 1, 1, 2, false },
{ 1, 0, 1, 1, 2, 2, 2, false },
{ 2, 1, -1, 2, 2, 2, 2, false },
};
} else if (shaderInfo.scaleFactor == 4) {
multipassScratchDescs_ = {
{ "nnedi3_vertical", 1, 2 },
{ "nnedi3_horizontal", 2, 2 },
{ "nnedi3_vertical2", 2, 4 },
{ "nnedi3_horizontal2", 4, 4 },
};
multipassStageDescs_ = {
{ 0, -1, 0, 1, 1, 1, 2, false },
{ 2, 0, 1, 1, 2, 2, 2, false },
{ 1, 1, 2, 2, 2, 2, 4, false },
{ 2, 2, 3, 2, 4, 4, 4, false },
{ 3, 3, -1, 4, 4, 4, 4, true },
};
} else {
if (!GetTextureScaleMultipassLayout(shaderInfo.scaleFactor, &multipassScratchDescs_, &multipassStageDescs_)) {
ERROR_LOG(Log::G3D, "Unsupported multipass scale factor %d in section '%s'", shaderInfo.scaleFactor, shaderInfo.section.c_str());
return false;
}
@@ -395,7 +317,7 @@ void TextureCacheVulkan::CompileScalingShader() {
}
} else {
std::string shaderSource = ReadShaderSrc(shaderInfo->computeShaderFiles[0]);
std::string fullUploadShader = StringFromFormat(uploadShader, shaderSource.c_str());
std::string fullUploadShader = StringFromFormat(textureScaleUploadShader, shaderSource.c_str());
singlePassCS_ = CompileShaderModule(vulkan, VK_SHADER_STAGE_COMPUTE_BIT, fullUploadShader.c_str(), &error);
_dbg_assert_msg_(singlePassCS_ != VK_NULL_HANDLE, "failed to compile upload shader");
if (singlePassCS_ == VK_NULL_HANDLE)
+2 -16
View File
@@ -101,22 +101,8 @@ private:
MULTIPASS,
};
struct MultipassScratchDesc {
const char *tag;
uint8_t widthScale;
uint8_t heightScale;
};
struct MultipassStageDesc {
uint8_t shaderIndex;
int8_t inputScratch; // -1 means the original upload buffer.
int8_t outputScratch; // -1 means the final destination image.
uint8_t srcWidthScale;
uint8_t srcHeightScale;
uint8_t dstWidthScale;
uint8_t dstHeightScale;
bool useFinalOutputSize;
};
typedef TextureScaleScratchDesc MultipassScratchDesc;
typedef TextureScaleStageDesc MultipassStageDesc;
void LoadVulkanTextureLevel(TexCacheEntry &entry, uint8_t *writePtr, int rowPitch, int level, int scaleFactor, VkFormat dstFmt);
static VkFormat GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) ;