Merge pull request #22373 from hrydgard/video-texture-clamp

Clamp sampling of video textures and direct-displayed video to the 480x272 frame
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-09-28 13:46:00 -06:00
commit 91a34056d0
6 files changed
+44 -38

No files matched your search

+5 -3
View File
@@ -1546,7 +1546,9 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
bool FramebufferManagerCommon::DrawFramebufferToOutput(const DisplayLayoutConfig &config, const u8 *srcPixels, int srcStride, GEBufferFormat srcPixelFormat) {
textureCache_->ForgetLastTexture();
Draw::Texture *pixelsTex = MakePixelTexture(srcPixels, srcPixelFormat, srcStride, 512, 272);
// Upload exactly the displayed 480x272, so that filtering (and post shaders) clamp at the edge of the
// image instead of pulling in what's past it in memory - often garbage, like when this is video.
Draw::Texture *pixelsTex = MakePixelTexture(srcPixels, srcPixelFormat, srcStride, 480, 272);
if (!pixelsTex) {
return false;
}
@@ -1557,12 +1559,12 @@ bool FramebufferManagerCommon::DrawFramebufferToOutput(const DisplayLayoutConfig
flags |= OutputFlags::BACKBUFFER_FLIPPED;
}
constexpr float u0 = 0.0f, u1 = 480.0f / 512.0f;
constexpr float u0 = 0.0f, u1 = 1.0f;
constexpr float v0 = 0.0f, v1 = 1.0f;
if (useBufferedRendering_) {
presentation_->UpdateUniforms(gpu->VideoIsPlaying());
presentation_->SourceTexture(pixelsTex, 512, 272);
presentation_->SourceTexture(pixelsTex, 480, 272);
presentation_->RunPostshaderPasses(config, flags, uvRotation, u0, v0, u1, v1);
}
+3 -2
View File
@@ -337,8 +337,9 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip
if (gstate_c.needShaderTexClamp) {
// 4 bits total.
id.SetBit(FS_BIT_SHADER_TEX_CLAMP);
id.SetBit(FS_BIT_CLAMP_S, gstate.isTexCoordClampedS());
id.SetBit(FS_BIT_CLAMP_T, gstate.isTexCoordClampedT());
// Video is always clamped, so it can't wrap around into the garbage outside the frame.
id.SetBit(FS_BIT_CLAMP_S, gstate.isTexCoordClampedS() || gstate_c.textureIsVideo);
id.SetBit(FS_BIT_CLAMP_T, gstate.isTexCoordClampedT() || gstate_c.textureIsVideo);
}
id.SetBits(FS_BIT_SHADER_DEPAL_MODE, 2, (int)shaderDepalMode);
id.SetBits(FS_BIT_SHADER_DEPAL_FORMAT, 3, (int)shaderDepalFormat);
+21 -14
View File
@@ -32,6 +32,26 @@ void UpdateRotation(float rotMatrix[4], bool useBufferedRendering) {
}
}
void CalcTexClamp(float texClamp[4], float texClampOffset[2]) {
const float invW = 1.0f / (float)gstate_c.curTextureWidth;
const float invH = 1.0f / (float)gstate_c.curTextureHeight;
int w = gstate.getTextureWidth(0);
int h = gstate.getTextureHeight(0);
if (gstate_c.textureIsVideo) {
// Only the video frame itself, whatever is in memory around it may be garbage.
w = std::min(w, 480);
h = std::min(h, 272);
}
// First wrap xy, then half texel xy (for clamp.)
texClamp[0] = (float)w * invW;
texClamp[1] = (float)h * invH;
texClamp[2] = invW * 0.5f;
texClamp[3] = invH * 0.5f;
texClampOffset[0] = gstate_c.curTextureXOffset * invW;
texClampOffset[1] = gstate_c.curTextureYOffset * invH;
}
void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool useBufferedRendering, bool pixelMapped) {
if (dirtyUniforms & DIRTY_TEXENV) {
Uint8x3ToFloat3(ub->texEnvColor, gstate.texenvcolor);
@@ -50,20 +70,7 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool useBuffe
Uint8x3ToFloat3(ub->blendFixB, gstate.getFixB());
}
if (dirtyUniforms & DIRTY_TEXCLAMP) {
const float invW = 1.0f / (float)gstate_c.curTextureWidth;
const float invH = 1.0f / (float)gstate_c.curTextureHeight;
const int w = gstate.getTextureWidth(0);
const int h = gstate.getTextureHeight(0);
const float widthFactor = (float)w * invW;
const float heightFactor = (float)h * invH;
// First wrap xy, then half texel xy (for clamp.)
ub->texClamp[0] = widthFactor;
ub->texClamp[1] = heightFactor;
ub->texClamp[2] = invW * 0.5f;
ub->texClamp[3] = invH * 0.5f;
ub->texClampOffset[0] = gstate_c.curTextureXOffset * invW;
ub->texClampOffset[1] = gstate_c.curTextureYOffset * invH;
CalcTexClamp(ub->texClamp, ub->texClampOffset);
}
if (dirtyUniforms & DIRTY_MIPBIAS) {
+3
View File
@@ -113,6 +113,9 @@ uint32_t PackDepalBits(bool pixelMapped);
void UpdateFogCoef(const GEState &state, float fogCoef[2]);
// Computes u_texclamp and u_texclampoff. Only meaningful when gstate_c.needShaderTexClamp is set.
void CalcTexClamp(float texClamp[4], float texClampOffset[2]);
// This happens so much that I want it inline.
inline void UpdateUVScaleOff(const GEState &state, float uvScaleOff[4]) {
if (gstate_c.textureIsFramebuffer) {
+9 -1
View File
@@ -937,7 +937,15 @@ TextureApplyResult TextureCacheCommon::ApplyTexture(bool doBind) {
TextureApplyResult TextureCacheCommon::ApplyTextureFinish(TexCacheEntry *entry, bool doBind) {
_dbg_assert_(entry);
gstate_c.SetTextureIsVideo((entry->status & TexStatus::VIDEO) != 0);
const bool isVideo = (entry->status & TexStatus::VIDEO) != 0;
gstate_c.SetTextureIsVideo(isVideo);
if (isVideo) {
// Restrict sampling to the 480x272 frame (see CalcTexClamp), since with the forced linear
// filtering, whatever is next to it in memory bleeds in at the edges.
gstate_c.curTextureXOffset = 0;
gstate_c.curTextureYOffset = 0;
gstate_c.SetNeedShaderTexclamp(true);
}
gstate_c.SetTextureIsArray(false); // Ordinary 2D textures still aren't used by array view in VK. We probably might as well, though, at this point..
gstate_c.SetTextureIsFramebuffer(false);
entry->lastFrame = gpuStats.totals.numFlips;
+3 -18
View File
@@ -453,24 +453,9 @@ void LinkedShader::UpdateUniforms(const ShaderID &vsid, const ShaderLanguageDesc
}
if ((dirty & DIRTY_TEXCLAMP) && u_texclamp != -1) {
const float invW = 1.0f / (float)gstate_c.curTextureWidth;
const float invH = 1.0f / (float)gstate_c.curTextureHeight;
const int w = gstate.getTextureWidth(0);
const int h = gstate.getTextureHeight(0);
const float widthFactor = (float)w * invW;
const float heightFactor = (float)h * invH;
// First wrap xy, then half texel xy (for clamp.)
const float texclamp[4] = {
widthFactor,
heightFactor,
invW * 0.5f,
invH * 0.5f,
};
const float texclampoff[2] = {
gstate_c.curTextureXOffset * invW,
gstate_c.curTextureYOffset * invH,
};
float texclamp[4];
float texclampoff[2];
CalcTexClamp(texclamp, texclampoff);
render_->SetUniformF(&u_texclamp, 4, texclamp);
if (u_texclampoff != -1) {
render_->SetUniformF(&u_texclampoff, 2, texclampoff);