diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
index 2fc2a0ac6efe137f2a887c43f6ed3497bbe3cdbc..5bfdecc09fd79ededc988ed21a163932aca7e990 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
@@ -43,6 +43,10 @@ void EmitBitCastU64F64(EmitContext&, IR::Inst& inst, const IR::Value& value) {
     Alias(inst, value);
 }
 
+void EmitBitCastS32F32(EmitContext&, IR::Inst& inst, const IR::Value& value) {
+    Alias(inst, value);
+}
+
 void EmitBitCastF16U16(EmitContext&, IR::Inst& inst, const IR::Value& value) {
     Alias(inst, value);
 }
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
index c08f48ed9a7fc24b3261091f6c3b9e9d91be147d..d645fd532aafc2e063036bd57af4debb28d05b8f 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
@@ -196,6 +196,7 @@ void EmitSelectF64(EmitContext& ctx, ScalarS32 cond, Register true_value, Regist
 void EmitBitCastU16F16(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
 void EmitBitCastU32F32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
 void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
+void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
 void EmitBitCastF16U16(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
 void EmitBitCastF32U32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
 void EmitBitCastF64U64(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
index 1be4a0f5925ab72594bb73451bf5f861872c8ac0..8e5e6cf1f05f1169a6bd9036f47a126e7e1d546b 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
@@ -48,6 +48,10 @@ void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, std::string_view value)
     ctx.AddU64("{}=doubleBitsToUint64({});", inst, value);
 }
 
+void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value) {
+    ctx.AddF32("{}=ftoi({});", inst, value);
+}
+
 void EmitBitCastF16U16([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst) {
     NotImplemented();
 }
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
index 3c8bcb7e99ab94ab2a72555031846bb5cf32a1a2..96e683b5e0d1d2ebcfd1f694ec1e7ec4de7ea585 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
@@ -230,6 +230,7 @@ void EmitSelectF64(EmitContext& ctx, IR::Inst& inst, std::string_view cond,
 void EmitBitCastU16F16(EmitContext& ctx, IR::Inst& inst);
 void EmitBitCastU32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
 void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, std::string_view value);
+void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
 void EmitBitCastF16U16(EmitContext& ctx, IR::Inst& inst);
 void EmitBitCastF32U32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
 void EmitBitCastF64U64(EmitContext& ctx, IR::Inst& inst, std::string_view value);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
index c4ca28d11adeb8cf7801c7df93a1659445238ef4..50daacd95f3f92e1ddd7e0badac1e91cfa5a35bb 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
@@ -18,6 +18,10 @@ void EmitBitCastU64F64(EmitContext&) {
     throw NotImplementedException("SPIR-V Instruction");
 }
 
+void EmitBitCastS32F32(EmitContext&) {
+    throw NotImplementedException("SPIR-V Instruction");
+}
+
 void EmitBitCastF16U16(EmitContext&) {
     throw NotImplementedException("SPIR-V Instruction");
 }
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
index 86dd3c4f34ba19b97d8cbf36553d242e48981c94..7070c8fda78b0d63636e5e930848d600a1681e7c 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
@@ -178,7 +178,8 @@ Id EmitSelectF64(EmitContext& ctx, Id cond, Id true_value, Id false_value);
 void EmitBitCastU16F16(EmitContext& ctx);
 Id EmitBitCastU32F32(EmitContext& ctx, Id value);
 void EmitBitCastU64F64(EmitContext& ctx);
-void EmitBitCastF16U16(EmitContext& ctx);
+void EmitBitCastS32F32(EmitContext& ctx);
+void EmitBitCastF16U16(EmitContext&);
 Id EmitBitCastF32U32(EmitContext& ctx, Id value);
 void EmitBitCastF64U64(EmitContext& ctx);
 Id EmitPackUint2x32(EmitContext& ctx, Id value);
diff --git a/src/shader_recompiler/environment.h b/src/shader_recompiler/environment.h
index 980e0e54c1728c1d6f1b888c9a5c76188b6c3fea..402f2664f612ebee91df494aea8ae80d9f430197 100644
--- a/src/shader_recompiler/environment.h
+++ b/src/shader_recompiler/environment.h
@@ -22,6 +22,8 @@ public:
 
     [[nodiscard]] virtual TextureType ReadTextureType(u32 raw_handle) = 0;
 
+    [[nodiscard]] virtual TexturePixelFormat ReadTexturePixelFormat(u32 raw_handle) = 0;
+
     [[nodiscard]] virtual u32 ReadViewportTransformState() = 0;
 
     [[nodiscard]] virtual u32 TextureBoundBuffer() const = 0;
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
index de1ce90a314eba817a96390c4228f4041e49c2e0..d4425f06d9c7bd923adde7fe9d139bfe9a66c0f4 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
@@ -691,6 +691,11 @@ IR::U32 IREmitter::BitCast<IR::U32, IR::F32>(const IR::F32& value) {
     return Inst<IR::U32>(Opcode::BitCastU32F32, value);
 }
 
+template <>
+IR::S32 IREmitter::BitCast<IR::S32, IR::F32>(const IR::F32& value) {
+    return Inst<IR::S32>(Opcode::BitCastS32F32, value);
+}
+
 template <>
 IR::F32 IREmitter::BitCast<IR::F32, IR::U32>(const IR::U32& value) {
     return Inst<IR::F32>(Opcode::BitCastF32U32, value);
diff --git a/src/shader_recompiler/frontend/ir/opcodes.h b/src/shader_recompiler/frontend/ir/opcodes.h
index 752879a186a4d7ad2f6ac4af42fdaefd8c31005f..e70d7745cdecce9c4cc37dd2e288162921474bb1 100644
--- a/src/shader_recompiler/frontend/ir/opcodes.h
+++ b/src/shader_recompiler/frontend/ir/opcodes.h
@@ -37,6 +37,7 @@ constexpr Type U8{Type::U8};
 constexpr Type U16{Type::U16};
 constexpr Type U32{Type::U32};
 constexpr Type U64{Type::U64};
+constexpr Type S32{Type::S32};
 constexpr Type F16{Type::F16};
 constexpr Type F32{Type::F32};
 constexpr Type F64{Type::F64};
diff --git a/src/shader_recompiler/frontend/ir/opcodes.inc b/src/shader_recompiler/frontend/ir/opcodes.inc
index f3038f2984744d8d18217c9b4834234e10728f44..88aa077ee83f4562192f1686b785ae8f94fb5e48 100644
--- a/src/shader_recompiler/frontend/ir/opcodes.inc
+++ b/src/shader_recompiler/frontend/ir/opcodes.inc
@@ -174,6 +174,7 @@ OPCODE(SelectF64,                                           F64,            U1,
 OPCODE(BitCastU16F16,                                       U16,            F16,                                                                            )
 OPCODE(BitCastU32F32,                                       U32,            F32,                                                                            )
 OPCODE(BitCastU64F64,                                       U64,            F64,                                                                            )
+OPCODE(BitCastS32F32,                                       S32,            F32,                                                                            )
 OPCODE(BitCastF16U16,                                       F16,            U16,                                                                            )
 OPCODE(BitCastF32U32,                                       F32,            U32,                                                                            )
 OPCODE(BitCastF64U64,                                       F64,            U64,                                                                            )
diff --git a/src/shader_recompiler/frontend/ir/type.h b/src/shader_recompiler/frontend/ir/type.h
index 04c8c4ddbea8a8d1828688b620874ca59da10707..5a7c706ada718b7e9af73070841fabc13777320f 100644
--- a/src/shader_recompiler/frontend/ir/type.h
+++ b/src/shader_recompiler/frontend/ir/type.h
@@ -24,21 +24,22 @@ enum class Type {
     U16 = 1 << 7,
     U32 = 1 << 8,
     U64 = 1 << 9,
-    F16 = 1 << 10,
-    F32 = 1 << 11,
-    F64 = 1 << 12,
-    U32x2 = 1 << 13,
-    U32x3 = 1 << 14,
-    U32x4 = 1 << 15,
-    F16x2 = 1 << 16,
-    F16x3 = 1 << 17,
-    F16x4 = 1 << 18,
-    F32x2 = 1 << 19,
-    F32x3 = 1 << 20,
-    F32x4 = 1 << 21,
-    F64x2 = 1 << 22,
-    F64x3 = 1 << 23,
-    F64x4 = 1 << 24,
+    S32 = 1 << 10,
+    F16 = 1 << 11,
+    F32 = 1 << 12,
+    F64 = 1 << 13,
+    U32x2 = 1 << 14,
+    U32x3 = 1 << 15,
+    U32x4 = 1 << 16,
+    F16x2 = 1 << 17,
+    F16x3 = 1 << 18,
+    F16x4 = 1 << 19,
+    F32x2 = 1 << 20,
+    F32x3 = 1 << 21,
+    F32x4 = 1 << 22,
+    F64x2 = 1 << 23,
+    F64x3 = 1 << 24,
+    F64x4 = 1 << 25,
 };
 DECLARE_ENUM_FLAG_OPERATORS(Type)
 
diff --git a/src/shader_recompiler/frontend/ir/value.cpp b/src/shader_recompiler/frontend/ir/value.cpp
index 3461693283d6f84fda019a36f0304739a44a3667..30ba12316c7c15b64457ca40cd070392ed331f75 100644
--- a/src/shader_recompiler/frontend/ir/value.cpp
+++ b/src/shader_recompiler/frontend/ir/value.cpp
@@ -23,6 +23,8 @@ Value::Value(u16 value) noexcept : type{Type::U16}, imm_u16{value} {}
 
 Value::Value(u32 value) noexcept : type{Type::U32}, imm_u32{value} {}
 
+Value::Value(s32 value) noexcept : type{Type::S32}, imm_s32{value} {}
+
 Value::Value(f32 value) noexcept : type{Type::F32}, imm_f32{value} {}
 
 Value::Value(u64 value) noexcept : type{Type::U64}, imm_u64{value} {}
@@ -69,6 +71,7 @@ bool Value::operator==(const Value& other) const {
         return imm_u16 == other.imm_u16;
     case Type::U32:
     case Type::F32:
+    case Type::S32:
         return imm_u32 == other.imm_u32;
     case Type::U64:
     case Type::F64:
diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h
index 6a673ca05a060723cf9d7a4bdb2b455d2e56e6d3..e8bbb93a5db6a29f1bc7a61d772808cf4c2b4706 100644
--- a/src/shader_recompiler/frontend/ir/value.h
+++ b/src/shader_recompiler/frontend/ir/value.h
@@ -44,6 +44,7 @@ public:
     explicit Value(u8 value) noexcept;
     explicit Value(u16 value) noexcept;
     explicit Value(u32 value) noexcept;
+    explicit Value(s32 value) noexcept;
     explicit Value(f32 value) noexcept;
     explicit Value(u64 value) noexcept;
     explicit Value(f64 value) noexcept;
@@ -66,6 +67,7 @@ public:
     [[nodiscard]] u8 U8() const;
     [[nodiscard]] u16 U16() const;
     [[nodiscard]] u32 U32() const;
+    [[nodiscard]] s32 S32() const;
     [[nodiscard]] f32 F32() const;
     [[nodiscard]] u64 U64() const;
     [[nodiscard]] f64 F64() const;
@@ -85,6 +87,7 @@ private:
         u8 imm_u8;
         u16 imm_u16;
         u32 imm_u32;
+        s32 imm_s32;
         f32 imm_f32;
         u64 imm_u64;
         f64 imm_f64;
@@ -266,6 +269,7 @@ using U8 = TypedValue<Type::U8>;
 using U16 = TypedValue<Type::U16>;
 using U32 = TypedValue<Type::U32>;
 using U64 = TypedValue<Type::U64>;
+using S32 = TypedValue<Type::S32>;
 using F16 = TypedValue<Type::F16>;
 using F32 = TypedValue<Type::F32>;
 using F64 = TypedValue<Type::F64>;
@@ -377,6 +381,14 @@ inline u32 Value::U32() const {
     return imm_u32;
 }
 
+inline s32 Value::S32() const {
+    if (IsIdentity()) {
+        return inst->Arg(0).S32();
+    }
+    DEBUG_ASSERT(type == Type::S32);
+    return imm_s32;
+}
+
 inline f32 Value::F32() const {
     if (IsIdentity()) {
         return inst->Arg(0).F32();
diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp
index e8be58357bdce8be24538facdc4c658d09d97f7f..9eff84a3d5bbc38092f1ef97575b3b796372cfd5 100644
--- a/src/shader_recompiler/ir_opt/texture_pass.cpp
+++ b/src/shader_recompiler/ir_opt/texture_pass.cpp
@@ -7,6 +7,7 @@
 
 #include <boost/container/small_vector.hpp>
 
+#include "common/settings.h"
 #include "shader_recompiler/environment.h"
 #include "shader_recompiler/frontend/ir/basic_block.h"
 #include "shader_recompiler/frontend/ir/breadth_first_search.h"
@@ -363,6 +364,14 @@ TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
     return env.ReadTextureType(lhs_raw | rhs_raw);
 }
 
+TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) {
+    const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
+    const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
+    const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset)};
+    const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)};
+    return env.ReadTexturePixelFormat(lhs_raw | rhs_raw);
+}
+
 class Descriptors {
 public:
     explicit Descriptors(TextureBufferDescriptors& texture_buffer_descriptors_,
@@ -451,6 +460,38 @@ void PatchImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) {
                ir.FPMul(IR::F32(ir.CompositeExtract(coord, 1)),
                         ir.FPRecip(ir.ConvertUToF(32, 32, ir.CompositeExtract(texture_size, 1))))));
 }
+
+void PathTexelFetch(IR::Block& block, IR::Inst& inst, TexturePixelFormat pixel_format) {
+    const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
+    IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
+    auto get_max_value = [pixel_format]() -> float {
+        switch (pixel_format) {
+        case TexturePixelFormat::A8B8G8R8_SNORM:
+        case TexturePixelFormat::R8G8_SNORM:
+        case TexturePixelFormat::R8_SNORM:
+            return 1.f / std::numeric_limits<char>::max();
+        case TexturePixelFormat::R16G16B16A16_SNORM:
+        case TexturePixelFormat::R16G16_SNORM:
+        case TexturePixelFormat::R16_SNORM:
+            return 1.f / std::numeric_limits<short>::max();
+        default:
+            throw InvalidArgument("Invalid texture pixel format");
+        }
+    };
+
+    const IR::Value new_inst{&*block.PrependNewInst(it, inst)};
+    const IR::F32 x(ir.CompositeExtract(new_inst, 0));
+    const IR::F32 y(ir.CompositeExtract(new_inst, 1));
+    const IR::F32 z(ir.CompositeExtract(new_inst, 2));
+    const IR::F32 w(ir.CompositeExtract(new_inst, 3));
+    const IR::F16F32F64 max_value(ir.Imm32(get_max_value()));
+    const IR::Value converted =
+        ir.CompositeConstruct(ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(x)), max_value),
+                              ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(y)), max_value),
+                              ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(z)), max_value),
+                              ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(w)), max_value));
+    inst.ReplaceUsesWith(converted);
+}
 } // Anonymous namespace
 
 void TexturePass(Environment& env, IR::Program& program) {
@@ -597,6 +638,14 @@ void TexturePass(Environment& env, IR::Program& program) {
         } else {
             inst->SetArg(0, IR::Value{});
         }
+
+        if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL &&
+            inst->GetOpcode() == IR::Opcode::ImageFetch && flags.type == TextureType::Buffer) {
+            const auto pixel_format = ReadTexturePixelFormat(env, cbuf);
+            if (pixel_format != TexturePixelFormat::OTHER) {
+                PathTexelFetch(*texture_inst.block, *texture_inst.inst, pixel_format);
+            }
+        }
     }
 }
 
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h
index a479e105eab2d731d5581744ad05e108a1ec10eb..f31e1f821f5d2430ce081944fa2cc2a1bc018ec0 100644
--- a/src/shader_recompiler/shader_info.h
+++ b/src/shader_recompiler/shader_info.h
@@ -29,6 +29,16 @@ enum class TextureType : u32 {
 };
 constexpr u32 NUM_TEXTURE_TYPES = 9;
 
+enum class TexturePixelFormat : u32 {
+    A8B8G8R8_SNORM,
+    R8_SNORM,
+    R8G8_SNORM,
+    R16G16B16A16_SNORM,
+    R16G16_SNORM,
+    R16_SNORM,
+    OTHER
+};
+
 enum class ImageFormat : u32 {
     Typeless,
     R8_UINT,
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 08f4d69aba67de93db3ebaf17a33341f4b42e25a..6af4ae79316e5abf82ed56214180386a46a6cf3d 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -29,17 +29,17 @@ constexpr std::array PROGRAM_LUT{
 [[nodiscard]] GLenum GetTextureBufferFormat(GLenum gl_format) {
     switch (gl_format) {
     case GL_RGBA8_SNORM:
-        return GL_RGBA8;
+        return GL_RGBA8I;
     case GL_R8_SNORM:
-        return GL_R8;
+        return GL_R8I;
     case GL_RGBA16_SNORM:
-        return GL_RGBA16;
+        return GL_RGBA16I;
     case GL_R16_SNORM:
-        return GL_R16;
+        return GL_R16I;
     case GL_RG16_SNORM:
-        return GL_RG16;
+        return GL_RG16I;
     case GL_RG8_SNORM:
-        return GL_RG8;
+        return GL_RG8I;
     default:
         return gl_format;
     }
@@ -96,9 +96,6 @@ GLuint Buffer::View(u32 offset, u32 size, PixelFormat format) {
     texture.Create(GL_TEXTURE_BUFFER);
     const GLenum gl_format{MaxwellToGL::GetFormatTuple(format).internal_format};
     const GLenum texture_format{GetTextureBufferFormat(gl_format)};
-    if (texture_format != gl_format) {
-        LOG_WARNING(Render_OpenGL, "Emulating SNORM texture buffer with UNORM.");
-    }
     glTextureBufferRange(texture.handle, texture_format, buffer.handle, offset, size);
     views.push_back({
         .offset = offset,
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index daceb05f476d8db973b33f56f651ce1f1ba5b00f..c115dabe1c44f8c4a9e4db6e8b8b0946a9c24111 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
@@ -504,8 +504,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
             }
         }
         if (info.uses_render_area) {
-            const auto render_area_width(static_cast<GLfloat>(regs.render_area.width));
-            const auto render_area_height(static_cast<GLfloat>(regs.render_area.height));
+            const auto render_area_width(static_cast<GLfloat>(regs.surface_clip.width));
+            const auto render_area_height(static_cast<GLfloat>(regs.surface_clip.height));
             if (use_assembly) {
                 glProgramLocalParameter4fARB(AssemblyStage(stage), 1, render_area_width,
                                              render_area_height, 0.0f, 0.0f);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 79d7908d4e03f91975ae71c811b3821070b37557..72e314d39ac19a6f3305651547c24ef66a1dfcf5 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -618,11 +618,11 @@ void RasterizerOpenGL::SyncViewport() {
             }
             flags[Dirty::Viewport0 + index] = false;
 
-            if (!regs.viewport_transform_enabled) {
-                const auto x = static_cast<GLfloat>(regs.render_area.x);
-                const auto y = static_cast<GLfloat>(regs.render_area.y);
-                const auto width = static_cast<GLfloat>(regs.render_area.width);
-                const auto height = static_cast<GLfloat>(regs.render_area.height);
+            if (!regs.viewport_scale_offset_enbled) {
+                const auto x = static_cast<GLfloat>(regs.surface_clip.x);
+                const auto y = static_cast<GLfloat>(regs.surface_clip.y);
+                const auto width = static_cast<GLfloat>(regs.surface_clip.width);
+                const auto height = static_cast<GLfloat>(regs.surface_clip.height);
                 glViewportIndexedf(static_cast<GLuint>(index), x, y, width != 0.0f ? width : 1.0f,
                                    height != 0.0f ? height : 1.0f);
                 continue;
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index b4372a839e8e6ccd508e6d562ef4e2868458daa1..81b6c372d5f4e833b2e50950f1b920e591c8c85b 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -444,8 +444,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
         const auto& info{stage_infos[0]};
         if (info.uses_render_area) {
             render_area.uses_render_area = true;
-            render_area.words = {static_cast<float>(regs.render_area.width),
-                                 static_cast<float>(regs.render_area.height)};
+            render_area.words = {static_cast<float>(regs.surface_clip.width),
+                                 static_cast<float>(regs.surface_clip.height)};
         }
     }};
     if constexpr (Spec::enabled_stages[0]) {
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 5af3c930ba2e310dc90cee635c13cb0baa0d02c6..f79fa8313f6935ffcb3b037a2a79dafcea170f70 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -683,11 +683,11 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg
     if (!state_tracker.TouchViewports()) {
         return;
     }
-    if (!regs.viewport_transform_enabled) {
-        const auto x = static_cast<float>(regs.render_area.x);
-        const auto y = static_cast<float>(regs.render_area.y);
-        const auto width = static_cast<float>(regs.render_area.width);
-        const auto height = static_cast<float>(regs.render_area.height);
+    if (!regs.viewport_scale_offset_enbled) {
+        const auto x = static_cast<float>(regs.surface_clip.x);
+        const auto y = static_cast<float>(regs.surface_clip.y);
+        const auto width = static_cast<float>(regs.surface_clip.width);
+        const auto height = static_cast<float>(regs.surface_clip.height);
         VkViewport viewport{
             .x = x,
             .y = y,
diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp
index 63bcf933728f18ecbc9e6e89dd6b90e8db7db23b..37bb76b72663d64fcc333d49d5960df11e718f98 100644
--- a/src/video_core/shader_environment.cpp
+++ b/src/video_core/shader_environment.cpp
@@ -19,6 +19,7 @@
 #include "video_core/engines/kepler_compute.h"
 #include "video_core/memory_manager.h"
 #include "video_core/shader_environment.h"
+#include "video_core/texture_cache/format_lookup_table.h"
 #include "video_core/textures/texture.h"
 
 namespace VideoCommon {
@@ -33,7 +34,7 @@ static u64 MakeCbufKey(u32 index, u32 offset) {
     return (static_cast<u64>(index) << 32) | offset;
 }
 
-static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
+static Shader::TextureType ConvertTextureType(const Tegra::Texture::TICEntry& entry) {
     switch (entry.texture_type) {
     case Tegra::Texture::TextureType::Texture1D:
         return Shader::TextureType::Color1D;
@@ -59,6 +60,26 @@ static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
     }
 }
 
+static Shader::TexturePixelFormat ConvertTexturePixelFormat(const Tegra::Texture::TICEntry& entry) {
+    switch (PixelFormatFromTextureInfo(entry.format, entry.r_type, entry.g_type, entry.b_type,
+                                       entry.a_type, entry.srgb_conversion)) {
+    case VideoCore::Surface::PixelFormat::A8B8G8R8_SNORM:
+        return Shader::TexturePixelFormat::A8B8G8R8_SNORM;
+    case VideoCore::Surface::PixelFormat::R8_SNORM:
+        return Shader::TexturePixelFormat::R8_SNORM;
+    case VideoCore::Surface::PixelFormat::R8G8_SNORM:
+        return Shader::TexturePixelFormat::R8G8_SNORM;
+    case VideoCore::Surface::PixelFormat::R16G16B16A16_SNORM:
+        return Shader::TexturePixelFormat::R16G16B16A16_SNORM;
+    case VideoCore::Surface::PixelFormat::R16G16_SNORM:
+        return Shader::TexturePixelFormat::R16G16_SNORM;
+    case VideoCore::Surface::PixelFormat::R16_SNORM:
+        return Shader::TexturePixelFormat::R16_SNORM;
+    default:
+        return Shader::TexturePixelFormat::OTHER;
+    }
+}
+
 static std::string_view StageToPrefix(Shader::Stage stage) {
     switch (stage) {
     case Shader::Stage::VertexB:
@@ -178,10 +199,13 @@ void GenericEnvironment::Dump(u64 hash) {
 void GenericEnvironment::Serialize(std::ofstream& file) const {
     const u64 code_size{static_cast<u64>(CachedSize())};
     const u64 num_texture_types{static_cast<u64>(texture_types.size())};
+    const u64 num_texture_pixel_formats{static_cast<u64>(texture_pixel_formats.size())};
     const u64 num_cbuf_values{static_cast<u64>(cbuf_values.size())};
 
     file.write(reinterpret_cast<const char*>(&code_size), sizeof(code_size))
         .write(reinterpret_cast<const char*>(&num_texture_types), sizeof(num_texture_types))
+        .write(reinterpret_cast<const char*>(&num_texture_pixel_formats),
+               sizeof(num_texture_pixel_formats))
         .write(reinterpret_cast<const char*>(&num_cbuf_values), sizeof(num_cbuf_values))
         .write(reinterpret_cast<const char*>(&local_memory_size), sizeof(local_memory_size))
         .write(reinterpret_cast<const char*>(&texture_bound), sizeof(texture_bound))
@@ -196,6 +220,10 @@ void GenericEnvironment::Serialize(std::ofstream& file) const {
         file.write(reinterpret_cast<const char*>(&key), sizeof(key))
             .write(reinterpret_cast<const char*>(&type), sizeof(type));
     }
+    for (const auto& [key, format] : texture_pixel_formats) {
+        file.write(reinterpret_cast<const char*>(&key), sizeof(key))
+            .write(reinterpret_cast<const char*>(&format), sizeof(format));
+    }
     for (const auto& [key, type] : cbuf_values) {
         file.write(reinterpret_cast<const char*>(&key), sizeof(key))
             .write(reinterpret_cast<const char*>(&type), sizeof(type));
@@ -239,15 +267,13 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
     return std::nullopt;
 }
 
-Shader::TextureType GenericEnvironment::ReadTextureTypeImpl(GPUVAddr tic_addr, u32 tic_limit,
-                                                            bool via_header_index, u32 raw) {
+Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
+                                                             bool via_header_index, u32 raw) {
     const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
     const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
     Tegra::Texture::TICEntry entry;
     gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));
-    const Shader::TextureType result{ConvertType(entry)};
-    texture_types.emplace(raw, result);
-    return result;
+    return entry;
 }
 
 GraphicsEnvironment::GraphicsEnvironment(Tegra::Engines::Maxwell3D& maxwell3d_,
@@ -307,13 +333,26 @@ u32 GraphicsEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
 Shader::TextureType GraphicsEnvironment::ReadTextureType(u32 handle) {
     const auto& regs{maxwell3d->regs};
     const bool via_header_index{regs.sampler_binding == Maxwell::SamplerBinding::ViaHeaderBinding};
-    return ReadTextureTypeImpl(regs.tex_header.Address(), regs.tex_header.limit, via_header_index,
-                               handle);
+    auto entry =
+        ReadTextureInfo(regs.tex_header.Address(), regs.tex_header.limit, via_header_index, handle);
+    const Shader::TextureType result{ConvertTextureType(entry)};
+    texture_types.emplace(handle, result);
+    return result;
+}
+
+Shader::TexturePixelFormat GraphicsEnvironment::ReadTexturePixelFormat(u32 handle) {
+    const auto& regs{maxwell3d->regs};
+    const bool via_header_index{regs.sampler_binding == Maxwell::SamplerBinding::ViaHeaderBinding};
+    auto entry =
+        ReadTextureInfo(regs.tex_header.Address(), regs.tex_header.limit, via_header_index, handle);
+    const Shader::TexturePixelFormat result(ConvertTexturePixelFormat(entry));
+    texture_pixel_formats.emplace(handle, result);
+    return result;
 }
 
 u32 GraphicsEnvironment::ReadViewportTransformState() {
     const auto& regs{maxwell3d->regs};
-    viewport_transform_state = regs.viewport_transform_enabled;
+    viewport_transform_state = regs.viewport_scale_offset_enbled;
     return viewport_transform_state;
 }
 
@@ -345,7 +384,19 @@ u32 ComputeEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
 Shader::TextureType ComputeEnvironment::ReadTextureType(u32 handle) {
     const auto& regs{kepler_compute->regs};
     const auto& qmd{kepler_compute->launch_description};
-    return ReadTextureTypeImpl(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
+    auto entry = ReadTextureInfo(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
+    const Shader::TextureType result{ConvertTextureType(entry)};
+    texture_types.emplace(handle, result);
+    return result;
+}
+
+Shader::TexturePixelFormat ComputeEnvironment::ReadTexturePixelFormat(u32 handle) {
+    const auto& regs{kepler_compute->regs};
+    const auto& qmd{kepler_compute->launch_description};
+    auto entry = ReadTextureInfo(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
+    const Shader::TexturePixelFormat result(ConvertTexturePixelFormat(entry));
+    texture_pixel_formats.emplace(handle, result);
+    return result;
 }
 
 u32 ComputeEnvironment::ReadViewportTransformState() {
@@ -355,9 +406,12 @@ u32 ComputeEnvironment::ReadViewportTransformState() {
 void FileEnvironment::Deserialize(std::ifstream& file) {
     u64 code_size{};
     u64 num_texture_types{};
+    u64 num_texture_pixel_formats{};
     u64 num_cbuf_values{};
     file.read(reinterpret_cast<char*>(&code_size), sizeof(code_size))
         .read(reinterpret_cast<char*>(&num_texture_types), sizeof(num_texture_types))
+        .read(reinterpret_cast<char*>(&num_texture_pixel_formats),
+              sizeof(num_texture_pixel_formats))
         .read(reinterpret_cast<char*>(&num_cbuf_values), sizeof(num_cbuf_values))
         .read(reinterpret_cast<char*>(&local_memory_size), sizeof(local_memory_size))
         .read(reinterpret_cast<char*>(&texture_bound), sizeof(texture_bound))
@@ -375,6 +429,13 @@ void FileEnvironment::Deserialize(std::ifstream& file) {
             .read(reinterpret_cast<char*>(&type), sizeof(type));
         texture_types.emplace(key, type);
     }
+    for (size_t i = 0; i < num_texture_pixel_formats; ++i) {
+        u32 key;
+        Shader::TexturePixelFormat format;
+        file.read(reinterpret_cast<char*>(&key), sizeof(key))
+            .read(reinterpret_cast<char*>(&format), sizeof(format));
+        texture_pixel_formats.emplace(key, format);
+    }
     for (size_t i = 0; i < num_cbuf_values; ++i) {
         u64 key;
         u32 value;
@@ -422,6 +483,14 @@ Shader::TextureType FileEnvironment::ReadTextureType(u32 handle) {
     return it->second;
 }
 
+Shader::TexturePixelFormat FileEnvironment::ReadTexturePixelFormat(u32 handle) {
+    const auto it{texture_pixel_formats.find(handle)};
+    if (it == texture_pixel_formats.end()) {
+        throw Shader::LogicError("Uncached read texture pixel format");
+    }
+    return it->second;
+}
+
 u32 FileEnvironment::ReadViewportTransformState() {
     return viewport_transform_state;
 }
diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h
index a05833f3821fc994d8ee95c05ec623740e00af53..bb55b029fe040ef7343719691e6a862042aa5ca5 100644
--- a/src/video_core/shader_environment.h
+++ b/src/video_core/shader_environment.h
@@ -63,14 +63,15 @@ public:
 protected:
     std::optional<u64> TryFindSize();
 
-    Shader::TextureType ReadTextureTypeImpl(GPUVAddr tic_addr, u32 tic_limit, bool via_header_index,
-                                            u32 raw);
+    Tegra::Texture::TICEntry ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
+                                             bool via_header_index, u32 raw);
 
     Tegra::MemoryManager* gpu_memory{};
     GPUVAddr program_base{};
 
     std::vector<u64> code;
     std::unordered_map<u32, Shader::TextureType> texture_types;
+    std::unordered_map<u32, Shader::TexturePixelFormat> texture_pixel_formats;
     std::unordered_map<u64, u32> cbuf_values;
 
     u32 local_memory_size{};
@@ -104,6 +105,8 @@ public:
 
     Shader::TextureType ReadTextureType(u32 handle) override;
 
+    Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
+
     u32 ReadViewportTransformState() override;
 
 private:
@@ -124,6 +127,8 @@ public:
 
     Shader::TextureType ReadTextureType(u32 handle) override;
 
+    Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
+
     u32 ReadViewportTransformState() override;
 
 private:
@@ -149,6 +154,8 @@ public:
 
     [[nodiscard]] Shader::TextureType ReadTextureType(u32 handle) override;
 
+    [[nodiscard]] Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
+
     [[nodiscard]] u32 ReadViewportTransformState() override;
 
     [[nodiscard]] u32 LocalMemorySize() const override;
@@ -164,6 +171,7 @@ public:
 private:
     std::unique_ptr<u64[]> code;
     std::unordered_map<u32, Shader::TextureType> texture_types;
+    std::unordered_map<u32, Shader::TexturePixelFormat> texture_pixel_formats;
     std::unordered_map<u64, u32> cbuf_values;
     std::array<u32, 3> workgroup_size{};
     u32 local_memory_size{};
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index 1223df5a0abacc2f6b67064350dc670337847587..e8c908b42bbe9ef8ee0a6c136165ab997419b21f 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -516,7 +516,6 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
     const u32 num_blocks_per_layer = NumBlocks(level_size, tile_size);
     const u32 host_bytes_per_layer = num_blocks_per_layer * bytes_per_block;
 
-    UNIMPLEMENTED_IF(info.tile_width_spacing > 0);
     UNIMPLEMENTED_IF(copy.image_offset.x != 0);
     UNIMPLEMENTED_IF(copy.image_offset.y != 0);
     UNIMPLEMENTED_IF(copy.image_offset.z != 0);