diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 766bb4f79..e4a9471b8 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -98,6 +98,10 @@ union Attribute {
         BitField<22, 2, u64> element;
         BitField<24, 6, Index> index;
         BitField<47, 3, AttributeSize> size;
+
+        bool IsPhysical() const {
+            return element == 0 && static_cast<u64>(index.Value()) == 0;
+        }
     } fmt20;
 
     union {
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index 84db4d4dc..339692295 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -50,13 +50,16 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
         UNIMPLEMENTED_IF_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) != 0,
                              "Unaligned attribute loads are not supported");
 
+        const Node buffer{GetRegister(instr.gpr39)};
+
         u64 next_element = instr.attribute.fmt20.element;
         auto next_index = static_cast<u64>(instr.attribute.fmt20.index.Value());
 
         const auto LoadNextElement = [&](u32 reg_offset) {
-            const Node buffer = GetRegister(instr.gpr39);
-            const Node attribute =
-                GetInputAttribute(static_cast<Attribute::Index>(next_index), next_element, buffer);
+            const Node attribute{instr.attribute.fmt20.IsPhysical()
+                                     ? GetPhysicalInputAttribute(instr.gpr8, buffer)
+                                     : GetInputAttribute(static_cast<Attribute::Index>(next_index),
+                                                         next_element, buffer)};
 
             SetRegister(bb, instr.gpr0.Value() + reg_offset, attribute);
 
diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp
index 0307ae5b0..947a372a2 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -94,6 +94,11 @@ Node ShaderIR::GetInputAttribute(Attribute::Index index, u64 element, Node buffe
     return StoreNode(AbufNode(index, static_cast<u32>(element), buffer));
 }
 
+Node ShaderIR::GetPhysicalInputAttribute(Tegra::Shader::Register physical_address, Node buffer) {
+    use_physical_attributes = true;
+    return StoreNode(AbufNode(GetRegister(physical_address), buffer));
+}
+
 Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buffer) {
     if (index == Attribute::Index::ClipDistances0123 ||
         index == Attribute::Index::ClipDistances4567) {
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 6aff64394..3a1164d4f 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -469,6 +469,9 @@ public:
                                 Node buffer = {})
         : buffer{buffer}, index{index}, element{element} {}
 
+    explicit constexpr AbufNode(Node physical_address, Node buffer = {})
+        : physical_address{physical_address}, buffer{buffer} {}
+
     Tegra::Shader::Attribute::Index GetIndex() const {
         return index;
     }
@@ -481,10 +484,19 @@ public:
         return buffer;
     }
 
+    bool IsPhysicalBuffer() const {
+        return physical_address != nullptr;
+    }
+
+    Node GetPhysicalAddress() const {
+        return physical_address;
+    }
+
 private:
-    const Node buffer;
-    const Tegra::Shader::Attribute::Index index;
-    const u32 element;
+    Node physical_address{};
+    Node buffer{};
+    Tegra::Shader::Attribute::Index index{};
+    u32 element{};
 };
 
 /// Constant buffer node, usually mapped to uniform buffers in GLSL
@@ -691,6 +703,8 @@ private:
     Node GetPredicate(bool immediate);
     /// Generates a node representing an input attribute. Keeps track of used attributes.
     Node GetInputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer = {});
+    /// Generates a node representing a physical input attribute.
+    Node GetPhysicalInputAttribute(Tegra::Shader::Register physical_address, Node buffer = {});
     /// Generates a node representing an output attribute. Keeps track of used attributes.
     Node GetOutputAttribute(Tegra::Shader::Attribute::Index index, u64 element, Node buffer);
     /// Generates a node representing an internal flag