diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp
index ee79f0edf5..536548f361 100644
--- a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp
+++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp
@@ -18,8 +18,8 @@
 #include "citra_qt/util/util.h"
 #include "common/vector_math.h"
 #include "video_core/debug_utils/debug_utils.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
+#include "video_core/regs.h"
 #include "video_core/texture/texture_decode.h"
 
 namespace {
@@ -123,15 +123,16 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&
 void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
     const unsigned int command_id =
         list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt();
-    if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) ||
-        COMMAND_IN_RANGE(command_id, texture2)) {
+    if (COMMAND_IN_RANGE(command_id, texturing.texture0) ||
+        COMMAND_IN_RANGE(command_id, texturing.texture1) ||
+        COMMAND_IN_RANGE(command_id, texturing.texture2)) {
 
         unsigned texture_index;
-        if (COMMAND_IN_RANGE(command_id, texture0)) {
+        if (COMMAND_IN_RANGE(command_id, texturing.texture0)) {
             texture_index = 0;
-        } else if (COMMAND_IN_RANGE(command_id, texture1)) {
+        } else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) {
             texture_index = 1;
-        } else if (COMMAND_IN_RANGE(command_id, texture2)) {
+        } else if (COMMAND_IN_RANGE(command_id, texturing.texture2)) {
             texture_index = 2;
         } else {
             UNREACHABLE_MSG("Unknown texture command");
@@ -146,19 +147,20 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) {
 
     const unsigned int command_id =
         list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toUInt();
-    if (COMMAND_IN_RANGE(command_id, texture0) || COMMAND_IN_RANGE(command_id, texture1) ||
-        COMMAND_IN_RANGE(command_id, texture2)) {
+    if (COMMAND_IN_RANGE(command_id, texturing.texture0) ||
+        COMMAND_IN_RANGE(command_id, texturing.texture1) ||
+        COMMAND_IN_RANGE(command_id, texturing.texture2)) {
 
         unsigned texture_index;
-        if (COMMAND_IN_RANGE(command_id, texture0)) {
+        if (COMMAND_IN_RANGE(command_id, texturing.texture0)) {
             texture_index = 0;
-        } else if (COMMAND_IN_RANGE(command_id, texture1)) {
+        } else if (COMMAND_IN_RANGE(command_id, texturing.texture1)) {
             texture_index = 1;
         } else {
             texture_index = 2;
         }
 
-        const auto texture = Pica::g_state.regs.GetTextures()[texture_index];
+        const auto texture = Pica::g_state.regs.texturing.GetTextures()[texture_index];
         const auto config = texture.config;
         const auto format = texture.format;
 
diff --git a/src/citra_qt/debugger/graphics/graphics_surface.cpp b/src/citra_qt/debugger/graphics/graphics_surface.cpp
index bd82b00d4b..f83c1f96c5 100644
--- a/src/citra_qt/debugger/graphics/graphics_surface.cpp
+++ b/src/citra_qt/debugger/graphics/graphics_surface.cpp
@@ -16,8 +16,8 @@
 #include "common/color.h"
 #include "core/hw/gpu.h"
 #include "core/memory.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
+#include "video_core/regs.h"
 #include "video_core/texture/texture_decode.h"
 #include "video_core/utils.h"
 
@@ -414,30 +414,30 @@ void GraphicsSurfaceWidget::OnUpdate() {
         // TODO: Store a reference to the registers in the debug context instead of accessing them
         // directly...
 
-        const auto& framebuffer = Pica::g_state.regs.framebuffer;
+        const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer;
 
         surface_address = framebuffer.GetColorBufferPhysicalAddress();
         surface_width = framebuffer.GetWidth();
         surface_height = framebuffer.GetHeight();
 
         switch (framebuffer.color_format) {
-        case Pica::Regs::ColorFormat::RGBA8:
+        case Pica::FramebufferRegs::ColorFormat::RGBA8:
             surface_format = Format::RGBA8;
             break;
 
-        case Pica::Regs::ColorFormat::RGB8:
+        case Pica::FramebufferRegs::ColorFormat::RGB8:
             surface_format = Format::RGB8;
             break;
 
-        case Pica::Regs::ColorFormat::RGB5A1:
+        case Pica::FramebufferRegs::ColorFormat::RGB5A1:
             surface_format = Format::RGB5A1;
             break;
 
-        case Pica::Regs::ColorFormat::RGB565:
+        case Pica::FramebufferRegs::ColorFormat::RGB565:
             surface_format = Format::RGB565;
             break;
 
-        case Pica::Regs::ColorFormat::RGBA4:
+        case Pica::FramebufferRegs::ColorFormat::RGBA4:
             surface_format = Format::RGBA4;
             break;
 
@@ -450,22 +450,22 @@ void GraphicsSurfaceWidget::OnUpdate() {
     }
 
     case Source::DepthBuffer: {
-        const auto& framebuffer = Pica::g_state.regs.framebuffer;
+        const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer;
 
         surface_address = framebuffer.GetDepthBufferPhysicalAddress();
         surface_width = framebuffer.GetWidth();
         surface_height = framebuffer.GetHeight();
 
         switch (framebuffer.depth_format) {
-        case Pica::Regs::DepthFormat::D16:
+        case Pica::FramebufferRegs::DepthFormat::D16:
             surface_format = Format::D16;
             break;
 
-        case Pica::Regs::DepthFormat::D24:
+        case Pica::FramebufferRegs::DepthFormat::D24:
             surface_format = Format::D24;
             break;
 
-        case Pica::Regs::DepthFormat::D24S8:
+        case Pica::FramebufferRegs::DepthFormat::D24S8:
             surface_format = Format::D24X8;
             break;
 
@@ -478,14 +478,14 @@ void GraphicsSurfaceWidget::OnUpdate() {
     }
 
     case Source::StencilBuffer: {
-        const auto& framebuffer = Pica::g_state.regs.framebuffer;
+        const auto& framebuffer = Pica::g_state.regs.framebuffer.framebuffer;
 
         surface_address = framebuffer.GetDepthBufferPhysicalAddress();
         surface_width = framebuffer.GetWidth();
         surface_height = framebuffer.GetHeight();
 
         switch (framebuffer.depth_format) {
-        case Pica::Regs::DepthFormat::D24S8:
+        case Pica::FramebufferRegs::DepthFormat::D24S8:
             surface_format = Format::X24S8;
             break;
 
@@ -512,7 +512,7 @@ void GraphicsSurfaceWidget::OnUpdate() {
             break;
         }
 
-        const auto texture = Pica::g_state.regs.GetTextures()[texture_index];
+        const auto texture = Pica::g_state.regs.texturing.GetTextures()[texture_index];
         auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config, texture.format);
 
         surface_address = info.physical_address;
@@ -574,7 +574,7 @@ void GraphicsSurfaceWidget::OnUpdate() {
         info.physical_address = surface_address;
         info.width = surface_width;
         info.height = surface_height;
-        info.format = static_cast<Pica::Regs::TextureFormat>(surface_format);
+        info.format = static_cast<Pica::TexturingRegs::TextureFormat>(surface_format);
         info.SetDefaultStride();
 
         for (unsigned int y = 0; y < surface_height; ++y) {
@@ -689,7 +689,8 @@ void GraphicsSurfaceWidget::SaveSurface() {
 
 unsigned int GraphicsSurfaceWidget::NibblesPerPixel(GraphicsSurfaceWidget::Format format) {
     if (format <= Format::MaxTextureFormat) {
-        return Pica::Regs::NibblesPerPixel(static_cast<Pica::Regs::TextureFormat>(format));
+        return Pica::TexturingRegs::NibblesPerPixel(
+            static_cast<Pica::TexturingRegs::TextureFormat>(format));
     }
 
     switch (format) {
diff --git a/src/citra_qt/debugger/graphics/graphics_tracing.cpp b/src/citra_qt/debugger/graphics/graphics_tracing.cpp
index 17f1c5ce25..40d5bed51d 100644
--- a/src/citra_qt/debugger/graphics/graphics_tracing.cpp
+++ b/src/citra_qt/debugger/graphics/graphics_tracing.cpp
@@ -18,7 +18,6 @@
 #include "core/hw/lcd.h"
 #include "core/tracer/recorder.h"
 #include "nihstro/float24.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
 
 GraphicsTracingWidget::GraphicsTracingWidget(std::shared_ptr<Pica::DebugContext> debug_context,
diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp
index 489ec5f215..e3f3194dba 100644
--- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp
+++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp
@@ -16,7 +16,6 @@
 #include <QTreeView>
 #include "citra_qt/debugger/graphics/graphics_vertex_shader.h"
 #include "citra_qt/util/util.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
 #include "video_core/shader/debug_data.h"
 #include "video_core/shader/shader.h"
@@ -359,7 +358,7 @@ void GraphicsVertexShaderWidget::DumpShader() {
     auto& config = Pica::g_state.regs.vs;
 
     Pica::DebugUtils::DumpShader(filename.toStdString(), config, setup,
-                                 Pica::g_state.regs.vs_output_attributes);
+                                 Pica::g_state.regs.rasterizer.vs_output_attributes);
 }
 
 GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index ad984cd942..11bc61e144 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -5,6 +5,7 @@ set(SRCS
             pica.cpp
             primitive_assembly.cpp
             rasterizer.cpp
+            regs.cpp
             renderer_base.cpp
             renderer_opengl/gl_rasterizer.cpp
             renderer_opengl/gl_rasterizer_cache.cpp
@@ -32,6 +33,13 @@ set(HEADERS
             primitive_assembly.h
             rasterizer.h
             rasterizer_interface.h
+            regs.h
+            regs_framebuffer.h
+            regs_lighting.h
+            regs_pipeline.h
+            regs_rasterizer.h
+            regs_shader.h
+            regs_texturing.h
             renderer_base.h
             renderer_opengl/gl_rasterizer.h
             renderer_opengl/gl_rasterizer_cache.h
diff --git a/src/video_core/clipper.cpp b/src/video_core/clipper.cpp
index 0774ffc535..0f71bbd064 100644
--- a/src/video_core/clipper.cpp
+++ b/src/video_core/clipper.cpp
@@ -12,10 +12,10 @@
 #include "common/logging/log.h"
 #include "common/vector_math.h"
 #include "video_core/clipper.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
 #include "video_core/pica_types.h"
 #include "video_core/rasterizer.h"
+#include "video_core/regs.h"
 #include "video_core/shader/shader.h"
 
 using Pica::Rasterizer::Vertex;
@@ -64,10 +64,10 @@ static void InitScreenCoordinates(Vertex& vtx) {
     } viewport;
 
     const auto& regs = g_state.regs;
-    viewport.halfsize_x = float24::FromRaw(regs.viewport_size_x);
-    viewport.halfsize_y = float24::FromRaw(regs.viewport_size_y);
-    viewport.offset_x = float24::FromFloat32(static_cast<float>(regs.viewport_corner.x));
-    viewport.offset_y = float24::FromFloat32(static_cast<float>(regs.viewport_corner.y));
+    viewport.halfsize_x = float24::FromRaw(regs.rasterizer.viewport_size_x);
+    viewport.halfsize_y = float24::FromRaw(regs.rasterizer.viewport_size_y);
+    viewport.offset_x = float24::FromFloat32(static_cast<float>(regs.rasterizer.viewport_corner.x));
+    viewport.offset_y = float24::FromFloat32(static_cast<float>(regs.rasterizer.viewport_corner.y));
 
     float24 inv_w = float24::FromFloat32(1.f) / vtx.pos.w;
     vtx.color *= inv_w;
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index 4955ff9f96..91c0ca4e60 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -16,11 +16,11 @@
 #include "core/tracer/recorder.h"
 #include "video_core/command_processor.h"
 #include "video_core/debug_utils/debug_utils.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
 #include "video_core/pica_types.h"
 #include "video_core/primitive_assembly.h"
 #include "video_core/rasterizer_interface.h"
+#include "video_core/regs.h"
 #include "video_core/renderer_base.h"
 #include "video_core/shader/shader.h"
 #include "video_core/vertex_loader.h"
@@ -74,23 +74,23 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
         Service::GSP::SignalInterrupt(Service::GSP::InterruptId::P3D);
         break;
 
-    case PICA_REG_INDEX_WORKAROUND(triangle_topology, 0x25E):
-        g_state.primitive_assembler.Reconfigure(regs.triangle_topology);
+    case PICA_REG_INDEX(pipeline.triangle_topology):
+        g_state.primitive_assembler.Reconfigure(regs.pipeline.triangle_topology);
         break;
 
-    case PICA_REG_INDEX_WORKAROUND(restart_primitive, 0x25F):
+    case PICA_REG_INDEX(pipeline.restart_primitive):
         g_state.primitive_assembler.Reset();
         break;
 
-    case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.index, 0x232):
+    case PICA_REG_INDEX(pipeline.vs_default_attributes_setup.index):
         g_state.immediate.current_attribute = 0;
         default_attr_counter = 0;
         break;
 
     // Load default vertex input attributes
-    case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.set_value[0], 0x233):
-    case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.set_value[1], 0x234):
-    case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.set_value[2], 0x235): {
+    case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[0], 0x233):
+    case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[1], 0x234):
+    case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[2], 0x235): {
         // TODO: Does actual hardware indeed keep an intermediate buffer or does
         //       it directly write the values?
         default_attr_write_buffer[default_attr_counter++] = value;
@@ -102,7 +102,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
         if (default_attr_counter >= 3) {
             default_attr_counter = 0;
 
-            auto& setup = regs.vs_default_attributes_setup;
+            auto& setup = regs.pipeline.vs_default_attributes_setup;
 
             if (setup.index >= 16) {
                 LOG_ERROR(HW_GPU, "Invalid VS default attribute index %d", (int)setup.index);
@@ -137,7 +137,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
 
                 immediate_input.attr[immediate_attribute_id] = attribute;
 
-                if (immediate_attribute_id < regs.max_input_attrib_index) {
+                if (immediate_attribute_id < regs.pipeline.max_input_attrib_index) {
                     immediate_attribute_id += 1;
                 } else {
                     MICROPROFILE_SCOPE(GPU_Drawing);
@@ -165,15 +165,16 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
                     };
 
                     g_state.primitive_assembler.SubmitVertex(
-                        Shader::OutputVertex::FromAttributeBuffer(regs, output), AddTriangle);
+                        Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output),
+                        AddTriangle);
                 }
             }
         }
         break;
     }
 
-    case PICA_REG_INDEX(gpu_mode):
-        if (regs.gpu_mode == Regs::GPUMode::Configuring) {
+    case PICA_REG_INDEX(pipeline.gpu_mode):
+        if (regs.pipeline.gpu_mode == PipelineRegs::GPUMode::Configuring) {
             MICROPROFILE_SCOPE(GPU_Drawing);
 
             // Draw immediate mode triangles when GPU Mode is set to GPUMode::Configuring
@@ -185,19 +186,20 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
         }
         break;
 
-    case PICA_REG_INDEX_WORKAROUND(command_buffer.trigger[0], 0x23c):
-    case PICA_REG_INDEX_WORKAROUND(command_buffer.trigger[1], 0x23d): {
-        unsigned index = static_cast<unsigned>(id - PICA_REG_INDEX(command_buffer.trigger[0]));
-        u32* head_ptr =
-            (u32*)Memory::GetPhysicalPointer(regs.command_buffer.GetPhysicalAddress(index));
+    case PICA_REG_INDEX_WORKAROUND(pipeline.command_buffer.trigger[0], 0x23c):
+    case PICA_REG_INDEX_WORKAROUND(pipeline.command_buffer.trigger[1], 0x23d): {
+        unsigned index =
+            static_cast<unsigned>(id - PICA_REG_INDEX(pipeline.command_buffer.trigger[0]));
+        u32* head_ptr = (u32*)Memory::GetPhysicalPointer(
+            regs.pipeline.command_buffer.GetPhysicalAddress(index));
         g_state.cmd_list.head_ptr = g_state.cmd_list.current_ptr = head_ptr;
-        g_state.cmd_list.length = regs.command_buffer.GetSize(index) / sizeof(u32);
+        g_state.cmd_list.length = regs.pipeline.command_buffer.GetSize(index) / sizeof(u32);
         break;
     }
 
     // It seems like these trigger vertex rendering
-    case PICA_REG_INDEX(trigger_draw):
-    case PICA_REG_INDEX(trigger_draw_indexed): {
+    case PICA_REG_INDEX(pipeline.trigger_draw):
+    case PICA_REG_INDEX(pipeline.trigger_draw_indexed): {
         MICROPROFILE_SCOPE(GPU_Drawing);
 
 #if PICA_LOG_TEV
@@ -209,13 +211,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
         // Processes information about internal vertex attributes to figure out how a vertex is
         // loaded.
         // Later, these can be compiled and cached.
-        const u32 base_address = regs.vertex_attributes.GetPhysicalBaseAddress();
-        VertexLoader loader(regs);
+        const u32 base_address = regs.pipeline.vertex_attributes.GetPhysicalBaseAddress();
+        VertexLoader loader(regs.pipeline);
 
         // Load vertices
-        bool is_indexed = (id == PICA_REG_INDEX(trigger_draw_indexed));
+        bool is_indexed = (id == PICA_REG_INDEX(pipeline.trigger_draw_indexed));
 
-        const auto& index_info = regs.index_array;
+        const auto& index_info = regs.pipeline.index_array;
         const u8* index_address_8 = Memory::GetPhysicalPointer(base_address + index_info.offset);
         const u16* index_address_16 = reinterpret_cast<const u16*>(index_address_8);
         bool index_u16 = index_info.format != 0;
@@ -224,13 +226,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
 
         if (g_debug_context && g_debug_context->recorder) {
             for (int i = 0; i < 3; ++i) {
-                const auto texture = regs.GetTextures()[i];
+                const auto texture = regs.texturing.GetTextures()[i];
                 if (!texture.enabled)
                     continue;
 
                 u8* texture_data = Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress());
                 g_debug_context->recorder->MemoryAccessed(
-                    texture_data, Pica::Regs::NibblesPerPixel(texture.format) *
+                    texture_data, Pica::TexturingRegs::NibblesPerPixel(texture.format) *
                                       texture.config.width / 2 * texture.config.height,
                     texture.config.GetPhysicalAddress());
             }
@@ -253,11 +255,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
 
         shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset);
 
-        for (unsigned int index = 0; index < regs.num_vertices; ++index) {
+        for (unsigned int index = 0; index < regs.pipeline.num_vertices; ++index) {
             // Indexed rendering doesn't use the start offset
             unsigned int vertex =
                 is_indexed ? (index_u16 ? index_address_16[index] : index_address_8[index])
-                           : (index + regs.vertex_offset);
+                           : (index + regs.pipeline.vertex_offset);
 
             // -1 is a common special value used for primitive restart. Since it's unknown if
             // the PICA supports it, and it would mess up the caching, guard against it here.
@@ -295,7 +297,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
                 shader_unit.WriteOutput(regs.vs, output);
 
                 // Retrieve vertex from register data
-                output_vertex = Shader::OutputVertex::FromAttributeBuffer(regs, output);
+                output_vertex = Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output);
 
                 if (is_indexed) {
                     vertex_cache[vertex_cache_pos] = output_vertex;
@@ -437,16 +439,16 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
         break;
     }
 
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[0], 0xe8):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[1], 0xe9):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[2], 0xea):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[3], 0xeb):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[4], 0xec):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[5], 0xed):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[6], 0xee):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[7], 0xef): {
-        g_state.fog.lut[regs.fog_lut_offset % 128].raw = value;
-        regs.fog_lut_offset.Assign(regs.fog_lut_offset + 1);
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[0], 0xe8):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[1], 0xe9):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[2], 0xea):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[3], 0xeb):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[4], 0xec):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[5], 0xed):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[6], 0xee):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[7], 0xef): {
+        g_state.fog.lut[regs.texturing.fog_lut_offset % 128].raw = value;
+        regs.texturing.fog_lut_offset.Assign(regs.texturing.fog_lut_offset + 1);
         break;
     }
 
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
index 2d40f7d4f6..e164e83a18 100644
--- a/src/video_core/debug_utils/debug_utils.cpp
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -29,10 +29,10 @@
 #include "common/math_util.h"
 #include "common/vector_math.h"
 #include "video_core/debug_utils/debug_utils.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
 #include "video_core/pica_types.h"
 #include "video_core/rasterizer_interface.h"
+#include "video_core/regs.h"
 #include "video_core/renderer_base.h"
 #include "video_core/shader/shader.h"
 #include "video_core/texture/texture_decode.h"
@@ -88,9 +88,9 @@ std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global
 
 namespace DebugUtils {
 
-void DumpShader(const std::string& filename, const Regs::ShaderConfig& config,
+void DumpShader(const std::string& filename, const ShaderRegs& config,
                 const Shader::ShaderSetup& setup,
-                const Regs::VSOutputAttributes* output_attributes) {
+                const RasterizerRegs::VSOutputAttributes* output_attributes) {
     struct StuffToWrite {
         const u8* pointer;
         u32 size;
@@ -129,7 +129,7 @@ void DumpShader(const std::string& filename, const Regs::ShaderConfig& config,
     // This is put into a try-catch block to make sure we notice unknown configurations.
     std::vector<OutputRegisterInfo> output_info_table;
     for (unsigned i = 0; i < 7; ++i) {
-        using OutputAttributes = Pica::Regs::VSOutputAttributes;
+        using OutputAttributes = Pica::RasterizerRegs::VSOutputAttributes;
 
         // TODO: It's still unclear how the attribute components map to the register!
         //       Once we know that, this code probably will not make much sense anymore.
@@ -331,7 +331,7 @@ static void FlushIOFile(png_structp png_ptr) {
 }
 #endif
 
-void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) {
+void DumpTexture(const TexturingRegs::TextureConfig& texture_config, u8* data) {
 #ifndef HAVE_PNG
     return;
 #else
@@ -396,7 +396,7 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) {
             info.width = texture_config.width;
             info.height = texture_config.height;
             info.stride = row_stride;
-            info.format = g_state.regs.texture0_format;
+            info.format = g_state.regs.texturing.texture0_format;
             Math::Vec4<u8> texture_color = Pica::Texture::LookupTexture(data, x, y, info);
             buf[3 * x + y * row_stride] = texture_color.r();
             buf[3 * x + y * row_stride + 1] = texture_color.g();
@@ -434,8 +434,10 @@ static std::string ReplacePattern(const std::string& input, const std::string& p
     return ret;
 }
 
-static std::string GetTevStageConfigSourceString(const Pica::Regs::TevStageConfig::Source& source) {
-    using Source = Pica::Regs::TevStageConfig::Source;
+static std::string GetTevStageConfigSourceString(
+    const TexturingRegs::TevStageConfig::Source& source) {
+
+    using Source = TexturingRegs::TevStageConfig::Source;
     static const std::map<Source, std::string> source_map = {
         {Source::PrimaryColor, "PrimaryColor"},
         {Source::PrimaryFragmentColor, "PrimaryFragmentColor"},
@@ -457,9 +459,10 @@ static std::string GetTevStageConfigSourceString(const Pica::Regs::TevStageConfi
 }
 
 static std::string GetTevStageConfigColorSourceString(
-    const Pica::Regs::TevStageConfig::Source& source,
-    const Pica::Regs::TevStageConfig::ColorModifier modifier) {
-    using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier;
+    const TexturingRegs::TevStageConfig::Source& source,
+    const TexturingRegs::TevStageConfig::ColorModifier modifier) {
+
+    using ColorModifier = TexturingRegs::TevStageConfig::ColorModifier;
     static const std::map<ColorModifier, std::string> color_modifier_map = {
         {ColorModifier::SourceColor, "%source.rgb"},
         {ColorModifier::OneMinusSourceColor, "(1.0 - %source.rgb)"},
@@ -483,9 +486,10 @@ static std::string GetTevStageConfigColorSourceString(
 }
 
 static std::string GetTevStageConfigAlphaSourceString(
-    const Pica::Regs::TevStageConfig::Source& source,
-    const Pica::Regs::TevStageConfig::AlphaModifier modifier) {
-    using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier;
+    const TexturingRegs::TevStageConfig::Source& source,
+    const TexturingRegs::TevStageConfig::AlphaModifier modifier) {
+
+    using AlphaModifier = TexturingRegs::TevStageConfig::AlphaModifier;
     static const std::map<AlphaModifier, std::string> alpha_modifier_map = {
         {AlphaModifier::SourceAlpha, "%source.a"},
         {AlphaModifier::OneMinusSourceAlpha, "(1.0 - %source.a)"},
@@ -507,8 +511,9 @@ static std::string GetTevStageConfigAlphaSourceString(
 }
 
 static std::string GetTevStageConfigOperationString(
-    const Pica::Regs::TevStageConfig::Operation& operation) {
-    using Operation = Pica::Regs::TevStageConfig::Operation;
+    const TexturingRegs::TevStageConfig::Operation& operation) {
+
+    using Operation = TexturingRegs::TevStageConfig::Operation;
     static const std::map<Operation, std::string> combiner_map = {
         {Operation::Replace, "%source1"},
         {Operation::Modulate, "(%source1 * %source2)"},
@@ -528,7 +533,7 @@ static std::string GetTevStageConfigOperationString(
     return op_it->second;
 }
 
-std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage) {
+std::string GetTevStageConfigColorCombinerString(const TexturingRegs::TevStageConfig& tev_stage) {
     auto op_str = GetTevStageConfigOperationString(tev_stage.color_op);
     op_str = ReplacePattern(
         op_str, "%source1",
@@ -541,7 +546,7 @@ std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfi
         GetTevStageConfigColorSourceString(tev_stage.color_source3, tev_stage.color_modifier3));
 }
 
-std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage) {
+std::string GetTevStageConfigAlphaCombinerString(const TexturingRegs::TevStageConfig& tev_stage) {
     auto op_str = GetTevStageConfigOperationString(tev_stage.alpha_op);
     op_str = ReplacePattern(
         op_str, "%source1",
@@ -554,7 +559,7 @@ std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfi
         GetTevStageConfigAlphaSourceString(tev_stage.alpha_source3, tev_stage.alpha_modifier3));
 }
 
-void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages) {
+void DumpTevStageConfig(const std::array<TexturingRegs::TevStageConfig, 6>& stages) {
     std::string stage_info = "Tev setup:\n";
     for (size_t index = 0; index < stages.size(); ++index) {
         const auto& tev_stage = stages[index];
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
index 938a2e1b50..fd94bdbb8b 100644
--- a/src/video_core/debug_utils/debug_utils.h
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -17,7 +17,7 @@
 #include <vector>
 #include "common/common_types.h"
 #include "common/vector_math.h"
-#include "video_core/pica.h"
+#include "video_core/regs.h"
 
 namespace CiTrace {
 class Recorder;
@@ -182,9 +182,9 @@ namespace DebugUtils {
 #define PICA_DUMP_TEXTURES 0
 #define PICA_LOG_TEV 0
 
-void DumpShader(const std::string& filename, const Regs::ShaderConfig& config,
+void DumpShader(const std::string& filename, const ShaderRegs& config,
                 const Shader::ShaderSetup& setup,
-                const Regs::VSOutputAttributes* output_attributes);
+                const RasterizerRegs::VSOutputAttributes* output_attributes);
 
 // Utility class to log Pica commands.
 struct PicaTrace {
@@ -205,13 +205,13 @@ inline bool IsPicaTracing() {
 void OnPicaRegWrite(PicaTrace::Write write);
 std::unique_ptr<PicaTrace> FinishPicaTracing();
 
-void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data);
+void DumpTexture(const TexturingRegs::TextureConfig& texture_config, u8* data);
 
-std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage);
-std::string GetTevStageConfigAlphaCombinerString(const Pica::Regs::TevStageConfig& tev_stage);
+std::string GetTevStageConfigColorCombinerString(const TexturingRegs::TevStageConfig& tev_stage);
+std::string GetTevStageConfigAlphaCombinerString(const TexturingRegs::TevStageConfig& tev_stage);
 
 /// Dumps the Tev stage config to log at trace level
-void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig, 6>& stages);
+void DumpTevStageConfig(const std::array<TexturingRegs::TevStageConfig, 6>& stages);
 
 /**
  * Used in the vertex loader to merge access records. TODO: Investigate if actually useful.
diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp
index b4a77c632d..13f0a4ab92 100644
--- a/src/video_core/pica.cpp
+++ b/src/video_core/pica.cpp
@@ -3,497 +3,14 @@
 // Refer to the license.txt file included.
 
 #include <cstring>
-#include <iterator>
-#include <unordered_map>
-#include <utility>
 #include "video_core/pica.h"
 #include "video_core/pica_state.h"
-#include "video_core/primitive_assembly.h"
-#include "video_core/shader/shader.h"
+#include "video_core/regs.h"
 
 namespace Pica {
 
 State g_state;
 
-static const std::pair<u16, const char*> register_names[] = {
-    {0x010, "GPUREG_FINALIZE"},
-
-    {0x040, "GPUREG_FACECULLING_CONFIG"},
-    {0x041, "GPUREG_VIEWPORT_WIDTH"},
-    {0x042, "GPUREG_VIEWPORT_INVW"},
-    {0x043, "GPUREG_VIEWPORT_HEIGHT"},
-    {0x044, "GPUREG_VIEWPORT_INVH"},
-
-    {0x047, "GPUREG_FRAGOP_CLIP"},
-    {0x048, "GPUREG_FRAGOP_CLIP_DATA0"},
-    {0x049, "GPUREG_FRAGOP_CLIP_DATA1"},
-    {0x04A, "GPUREG_FRAGOP_CLIP_DATA2"},
-    {0x04B, "GPUREG_FRAGOP_CLIP_DATA3"},
-
-    {0x04D, "GPUREG_DEPTHMAP_SCALE"},
-    {0x04E, "GPUREG_DEPTHMAP_OFFSET"},
-    {0x04F, "GPUREG_SH_OUTMAP_TOTAL"},
-    {0x050, "GPUREG_SH_OUTMAP_O0"},
-    {0x051, "GPUREG_SH_OUTMAP_O1"},
-    {0x052, "GPUREG_SH_OUTMAP_O2"},
-    {0x053, "GPUREG_SH_OUTMAP_O3"},
-    {0x054, "GPUREG_SH_OUTMAP_O4"},
-    {0x055, "GPUREG_SH_OUTMAP_O5"},
-    {0x056, "GPUREG_SH_OUTMAP_O6"},
-
-    {0x061, "GPUREG_EARLYDEPTH_FUNC"},
-    {0x062, "GPUREG_EARLYDEPTH_TEST1"},
-    {0x063, "GPUREG_EARLYDEPTH_CLEAR"},
-    {0x064, "GPUREG_SH_OUTATTR_MODE"},
-    {0x065, "GPUREG_SCISSORTEST_MODE"},
-    {0x066, "GPUREG_SCISSORTEST_POS"},
-    {0x067, "GPUREG_SCISSORTEST_DIM"},
-    {0x068, "GPUREG_VIEWPORT_XY"},
-
-    {0x06A, "GPUREG_EARLYDEPTH_DATA"},
-
-    {0x06D, "GPUREG_DEPTHMAP_ENABLE"},
-    {0x06E, "GPUREG_RENDERBUF_DIM"},
-    {0x06F, "GPUREG_SH_OUTATTR_CLOCK"},
-
-    {0x080, "GPUREG_TEXUNIT_CONFIG"},
-    {0x081, "GPUREG_TEXUNIT0_BORDER_COLOR"},
-    {0x082, "GPUREG_TEXUNIT0_DIM"},
-    {0x083, "GPUREG_TEXUNIT0_PARAM"},
-    {0x084, "GPUREG_TEXUNIT0_LOD"},
-    {0x085, "GPUREG_TEXUNIT0_ADDR1"},
-    {0x086, "GPUREG_TEXUNIT0_ADDR2"},
-    {0x087, "GPUREG_TEXUNIT0_ADDR3"},
-    {0x088, "GPUREG_TEXUNIT0_ADDR4"},
-    {0x089, "GPUREG_TEXUNIT0_ADDR5"},
-    {0x08A, "GPUREG_TEXUNIT0_ADDR6"},
-    {0x08B, "GPUREG_TEXUNIT0_SHADOW"},
-
-    {0x08E, "GPUREG_TEXUNIT0_TYPE"},
-    {0x08F, "GPUREG_LIGHTING_ENABLE0"},
-
-    {0x091, "GPUREG_TEXUNIT1_BORDER_COLOR"},
-    {0x092, "GPUREG_TEXUNIT1_DIM"},
-    {0x093, "GPUREG_TEXUNIT1_PARAM"},
-    {0x094, "GPUREG_TEXUNIT1_LOD"},
-    {0x095, "GPUREG_TEXUNIT1_ADDR"},
-    {0x096, "GPUREG_TEXUNIT1_TYPE"},
-
-    {0x099, "GPUREG_TEXUNIT2_BORDER_COLOR"},
-    {0x09A, "GPUREG_TEXUNIT2_DIM"},
-    {0x09B, "GPUREG_TEXUNIT2_PARAM"},
-    {0x09C, "GPUREG_TEXUNIT2_LOD"},
-    {0x09D, "GPUREG_TEXUNIT2_ADDR"},
-    {0x09E, "GPUREG_TEXUNIT2_TYPE"},
-
-    {0x0A8, "GPUREG_TEXUNIT3_PROCTEX0"},
-    {0x0A9, "GPUREG_TEXUNIT3_PROCTEX1"},
-    {0x0AA, "GPUREG_TEXUNIT3_PROCTEX2"},
-    {0x0AB, "GPUREG_TEXUNIT3_PROCTEX3"},
-    {0x0AC, "GPUREG_TEXUNIT3_PROCTEX4"},
-    {0x0AD, "GPUREG_TEXUNIT3_PROCTEX5"},
-
-    {0x0AF, "GPUREG_PROCTEX_LUT"},
-    {0x0B0, "GPUREG_PROCTEX_LUT_DATA0"},
-    {0x0B1, "GPUREG_PROCTEX_LUT_DATA1"},
-    {0x0B2, "GPUREG_PROCTEX_LUT_DATA2"},
-    {0x0B3, "GPUREG_PROCTEX_LUT_DATA3"},
-    {0x0B4, "GPUREG_PROCTEX_LUT_DATA4"},
-    {0x0B5, "GPUREG_PROCTEX_LUT_DATA5"},
-    {0x0B6, "GPUREG_PROCTEX_LUT_DATA6"},
-    {0x0B7, "GPUREG_PROCTEX_LUT_DATA7"},
-
-    {0x0C0, "GPUREG_TEXENV0_SOURCE"},
-    {0x0C1, "GPUREG_TEXENV0_OPERAND"},
-    {0x0C2, "GPUREG_TEXENV0_COMBINER"},
-    {0x0C3, "GPUREG_TEXENV0_COLOR"},
-    {0x0C4, "GPUREG_TEXENV0_SCALE"},
-
-    {0x0C8, "GPUREG_TEXENV1_SOURCE"},
-    {0x0C9, "GPUREG_TEXENV1_OPERAND"},
-    {0x0CA, "GPUREG_TEXENV1_COMBINER"},
-    {0x0CB, "GPUREG_TEXENV1_COLOR"},
-    {0x0CC, "GPUREG_TEXENV1_SCALE"},
-
-    {0x0D0, "GPUREG_TEXENV2_SOURCE"},
-    {0x0D1, "GPUREG_TEXENV2_OPERAND"},
-    {0x0D2, "GPUREG_TEXENV2_COMBINER"},
-    {0x0D3, "GPUREG_TEXENV2_COLOR"},
-    {0x0D4, "GPUREG_TEXENV2_SCALE"},
-
-    {0x0D8, "GPUREG_TEXENV3_SOURCE"},
-    {0x0D9, "GPUREG_TEXENV3_OPERAND"},
-    {0x0DA, "GPUREG_TEXENV3_COMBINER"},
-    {0x0DB, "GPUREG_TEXENV3_COLOR"},
-    {0x0DC, "GPUREG_TEXENV3_SCALE"},
-
-    {0x0E0, "GPUREG_TEXENV_UPDATE_BUFFER"},
-    {0x0E1, "GPUREG_FOG_COLOR"},
-
-    {0x0E4, "GPUREG_GAS_ATTENUATION"},
-    {0x0E5, "GPUREG_GAS_ACCMAX"},
-    {0x0E6, "GPUREG_FOG_LUT_INDEX"},
-
-    {0x0E8, "GPUREG_FOG_LUT_DATA0"},
-    {0x0E9, "GPUREG_FOG_LUT_DATA1"},
-    {0x0EA, "GPUREG_FOG_LUT_DATA2"},
-    {0x0EB, "GPUREG_FOG_LUT_DATA3"},
-    {0x0EC, "GPUREG_FOG_LUT_DATA4"},
-    {0x0ED, "GPUREG_FOG_LUT_DATA5"},
-    {0x0EE, "GPUREG_FOG_LUT_DATA6"},
-    {0x0EF, "GPUREG_FOG_LUT_DATA7"},
-    {0x0F0, "GPUREG_TEXENV4_SOURCE"},
-    {0x0F1, "GPUREG_TEXENV4_OPERAND"},
-    {0x0F2, "GPUREG_TEXENV4_COMBINER"},
-    {0x0F3, "GPUREG_TEXENV4_COLOR"},
-    {0x0F4, "GPUREG_TEXENV4_SCALE"},
-
-    {0x0F8, "GPUREG_TEXENV5_SOURCE"},
-    {0x0F9, "GPUREG_TEXENV5_OPERAND"},
-    {0x0FA, "GPUREG_TEXENV5_COMBINER"},
-    {0x0FB, "GPUREG_TEXENV5_COLOR"},
-    {0x0FC, "GPUREG_TEXENV5_SCALE"},
-    {0x0FD, "GPUREG_TEXENV_BUFFER_COLOR"},
-
-    {0x100, "GPUREG_COLOR_OPERATION"},
-    {0x101, "GPUREG_BLEND_FUNC"},
-    {0x102, "GPUREG_LOGIC_OP"},
-    {0x103, "GPUREG_BLEND_COLOR"},
-    {0x104, "GPUREG_FRAGOP_ALPHA_TEST"},
-    {0x105, "GPUREG_STENCIL_TEST"},
-    {0x106, "GPUREG_STENCIL_OP"},
-    {0x107, "GPUREG_DEPTH_COLOR_MASK"},
-
-    {0x110, "GPUREG_FRAMEBUFFER_INVALIDATE"},
-    {0x111, "GPUREG_FRAMEBUFFER_FLUSH"},
-    {0x112, "GPUREG_COLORBUFFER_READ"},
-    {0x113, "GPUREG_COLORBUFFER_WRITE"},
-    {0x114, "GPUREG_DEPTHBUFFER_READ"},
-    {0x115, "GPUREG_DEPTHBUFFER_WRITE"},
-    {0x116, "GPUREG_DEPTHBUFFER_FORMAT"},
-    {0x117, "GPUREG_COLORBUFFER_FORMAT"},
-    {0x118, "GPUREG_EARLYDEPTH_TEST2"},
-
-    {0x11B, "GPUREG_FRAMEBUFFER_BLOCK32"},
-    {0x11C, "GPUREG_DEPTHBUFFER_LOC"},
-    {0x11D, "GPUREG_COLORBUFFER_LOC"},
-    {0x11E, "GPUREG_FRAMEBUFFER_DIM"},
-
-    {0x120, "GPUREG_GAS_LIGHT_XY"},
-    {0x121, "GPUREG_GAS_LIGHT_Z"},
-    {0x122, "GPUREG_GAS_LIGHT_Z_COLOR"},
-    {0x123, "GPUREG_GAS_LUT_INDEX"},
-    {0x124, "GPUREG_GAS_LUT_DATA"},
-
-    {0x126, "GPUREG_GAS_DELTAZ_DEPTH"},
-
-    {0x130, "GPUREG_FRAGOP_SHADOW"},
-
-    {0x140, "GPUREG_LIGHT0_SPECULAR0"},
-    {0x141, "GPUREG_LIGHT0_SPECULAR1"},
-    {0x142, "GPUREG_LIGHT0_DIFFUSE"},
-    {0x143, "GPUREG_LIGHT0_AMBIENT"},
-    {0x144, "GPUREG_LIGHT0_XY"},
-    {0x145, "GPUREG_LIGHT0_Z"},
-    {0x146, "GPUREG_LIGHT0_SPOTDIR_XY"},
-    {0x147, "GPUREG_LIGHT0_SPOTDIR_Z"},
-
-    {0x149, "GPUREG_LIGHT0_CONFIG"},
-    {0x14A, "GPUREG_LIGHT0_ATTENUATION_BIAS"},
-    {0x14B, "GPUREG_LIGHT0_ATTENUATION_SCALE"},
-
-    {0x150, "GPUREG_LIGHT1_SPECULAR0"},
-    {0x151, "GPUREG_LIGHT1_SPECULAR1"},
-    {0x152, "GPUREG_LIGHT1_DIFFUSE"},
-    {0x153, "GPUREG_LIGHT1_AMBIENT"},
-    {0x154, "GPUREG_LIGHT1_XY"},
-    {0x155, "GPUREG_LIGHT1_Z"},
-    {0x156, "GPUREG_LIGHT1_SPOTDIR_XY"},
-    {0x157, "GPUREG_LIGHT1_SPOTDIR_Z"},
-
-    {0x159, "GPUREG_LIGHT1_CONFIG"},
-    {0x15A, "GPUREG_LIGHT1_ATTENUATION_BIAS"},
-    {0x15B, "GPUREG_LIGHT1_ATTENUATION_SCALE"},
-
-    {0x160, "GPUREG_LIGHT2_SPECULAR0"},
-    {0x161, "GPUREG_LIGHT2_SPECULAR1"},
-    {0x162, "GPUREG_LIGHT2_DIFFUSE"},
-    {0x163, "GPUREG_LIGHT2_AMBIENT"},
-    {0x164, "GPUREG_LIGHT2_XY"},
-    {0x165, "GPUREG_LIGHT2_Z"},
-    {0x166, "GPUREG_LIGHT2_SPOTDIR_XY"},
-    {0x167, "GPUREG_LIGHT2_SPOTDIR_Z"},
-
-    {0x169, "GPUREG_LIGHT2_CONFIG"},
-    {0x16A, "GPUREG_LIGHT2_ATTENUATION_BIAS"},
-    {0x16B, "GPUREG_LIGHT2_ATTENUATION_SCALE"},
-
-    {0x170, "GPUREG_LIGHT3_SPECULAR0"},
-    {0x171, "GPUREG_LIGHT3_SPECULAR1"},
-    {0x172, "GPUREG_LIGHT3_DIFFUSE"},
-    {0x173, "GPUREG_LIGHT3_AMBIENT"},
-    {0x174, "GPUREG_LIGHT3_XY"},
-    {0x175, "GPUREG_LIGHT3_Z"},
-    {0x176, "GPUREG_LIGHT3_SPOTDIR_XY"},
-    {0x177, "GPUREG_LIGHT3_SPOTDIR_Z"},
-
-    {0x179, "GPUREG_LIGHT3_CONFIG"},
-    {0x17A, "GPUREG_LIGHT3_ATTENUATION_BIAS"},
-    {0x17B, "GPUREG_LIGHT3_ATTENUATION_SCALE"},
-
-    {0x180, "GPUREG_LIGHT4_SPECULAR0"},
-    {0x181, "GPUREG_LIGHT4_SPECULAR1"},
-    {0x182, "GPUREG_LIGHT4_DIFFUSE"},
-    {0x183, "GPUREG_LIGHT4_AMBIENT"},
-    {0x184, "GPUREG_LIGHT4_XY"},
-    {0x185, "GPUREG_LIGHT4_Z"},
-    {0x186, "GPUREG_LIGHT4_SPOTDIR_XY"},
-    {0x187, "GPUREG_LIGHT4_SPOTDIR_Z"},
-
-    {0x189, "GPUREG_LIGHT4_CONFIG"},
-    {0x18A, "GPUREG_LIGHT4_ATTENUATION_BIAS"},
-    {0x18B, "GPUREG_LIGHT4_ATTENUATION_SCALE"},
-
-    {0x190, "GPUREG_LIGHT5_SPECULAR0"},
-    {0x191, "GPUREG_LIGHT5_SPECULAR1"},
-    {0x192, "GPUREG_LIGHT5_DIFFUSE"},
-    {0x193, "GPUREG_LIGHT5_AMBIENT"},
-    {0x194, "GPUREG_LIGHT5_XY"},
-    {0x195, "GPUREG_LIGHT5_Z"},
-    {0x196, "GPUREG_LIGHT5_SPOTDIR_XY"},
-    {0x197, "GPUREG_LIGHT5_SPOTDIR_Z"},
-
-    {0x199, "GPUREG_LIGHT5_CONFIG"},
-    {0x19A, "GPUREG_LIGHT5_ATTENUATION_BIAS"},
-    {0x19B, "GPUREG_LIGHT5_ATTENUATION_SCALE"},
-
-    {0x1A0, "GPUREG_LIGHT6_SPECULAR0"},
-    {0x1A1, "GPUREG_LIGHT6_SPECULAR1"},
-    {0x1A2, "GPUREG_LIGHT6_DIFFUSE"},
-    {0x1A3, "GPUREG_LIGHT6_AMBIENT"},
-    {0x1A4, "GPUREG_LIGHT6_XY"},
-    {0x1A5, "GPUREG_LIGHT6_Z"},
-    {0x1A6, "GPUREG_LIGHT6_SPOTDIR_XY"},
-    {0x1A7, "GPUREG_LIGHT6_SPOTDIR_Z"},
-
-    {0x1A9, "GPUREG_LIGHT6_CONFIG"},
-    {0x1AA, "GPUREG_LIGHT6_ATTENUATION_BIAS"},
-    {0x1AB, "GPUREG_LIGHT6_ATTENUATION_SCALE"},
-
-    {0x1B0, "GPUREG_LIGHT7_SPECULAR0"},
-    {0x1B1, "GPUREG_LIGHT7_SPECULAR1"},
-    {0x1B2, "GPUREG_LIGHT7_DIFFUSE"},
-    {0x1B3, "GPUREG_LIGHT7_AMBIENT"},
-    {0x1B4, "GPUREG_LIGHT7_XY"},
-    {0x1B5, "GPUREG_LIGHT7_Z"},
-    {0x1B6, "GPUREG_LIGHT7_SPOTDIR_XY"},
-    {0x1B7, "GPUREG_LIGHT7_SPOTDIR_Z"},
-
-    {0x1B9, "GPUREG_LIGHT7_CONFIG"},
-    {0x1BA, "GPUREG_LIGHT7_ATTENUATION_BIAS"},
-    {0x1BB, "GPUREG_LIGHT7_ATTENUATION_SCALE"},
-
-    {0x1C0, "GPUREG_LIGHTING_AMBIENT"},
-
-    {0x1C2, "GPUREG_LIGHTING_NUM_LIGHTS"},
-    {0x1C3, "GPUREG_LIGHTING_CONFIG0"},
-    {0x1C4, "GPUREG_LIGHTING_CONFIG1"},
-    {0x1C5, "GPUREG_LIGHTING_LUT_INDEX"},
-    {0x1C6, "GPUREG_LIGHTING_ENABLE1"},
-
-    {0x1C8, "GPUREG_LIGHTING_LUT_DATA0"},
-    {0x1C9, "GPUREG_LIGHTING_LUT_DATA1"},
-    {0x1CA, "GPUREG_LIGHTING_LUT_DATA2"},
-    {0x1CB, "GPUREG_LIGHTING_LUT_DATA3"},
-    {0x1CC, "GPUREG_LIGHTING_LUT_DATA4"},
-    {0x1CD, "GPUREG_LIGHTING_LUT_DATA5"},
-    {0x1CE, "GPUREG_LIGHTING_LUT_DATA6"},
-    {0x1CF, "GPUREG_LIGHTING_LUT_DATA7"},
-    {0x1D0, "GPUREG_LIGHTING_LUTINPUT_ABS"},
-    {0x1D1, "GPUREG_LIGHTING_LUTINPUT_SELECT"},
-    {0x1D2, "GPUREG_LIGHTING_LUTINPUT_SCALE"},
-
-    {0x1D9, "GPUREG_LIGHTING_LIGHT_PERMUTATION"},
-
-    {0x200, "GPUREG_ATTRIBBUFFERS_LOC"},
-    {0x201, "GPUREG_ATTRIBBUFFERS_FORMAT_LOW"},
-    {0x202, "GPUREG_ATTRIBBUFFERS_FORMAT_HIGH"},
-    {0x203, "GPUREG_ATTRIBBUFFER0_OFFSET"},
-    {0x204, "GPUREG_ATTRIBBUFFER0_CONFIG1"},
-    {0x205, "GPUREG_ATTRIBBUFFER0_CONFIG2"},
-    {0x206, "GPUREG_ATTRIBBUFFER1_OFFSET"},
-    {0x207, "GPUREG_ATTRIBBUFFER1_CONFIG1"},
-    {0x208, "GPUREG_ATTRIBBUFFER1_CONFIG2"},
-    {0x209, "GPUREG_ATTRIBBUFFER2_OFFSET"},
-    {0x20A, "GPUREG_ATTRIBBUFFER2_CONFIG1"},
-    {0x20B, "GPUREG_ATTRIBBUFFER2_CONFIG2"},
-    {0x20C, "GPUREG_ATTRIBBUFFER3_OFFSET"},
-    {0x20D, "GPUREG_ATTRIBBUFFER3_CONFIG1"},
-    {0x20E, "GPUREG_ATTRIBBUFFER3_CONFIG2"},
-    {0x20F, "GPUREG_ATTRIBBUFFER4_OFFSET"},
-    {0x210, "GPUREG_ATTRIBBUFFER4_CONFIG1"},
-    {0x211, "GPUREG_ATTRIBBUFFER4_CONFIG2"},
-    {0x212, "GPUREG_ATTRIBBUFFER5_OFFSET"},
-    {0x213, "GPUREG_ATTRIBBUFFER5_CONFIG1"},
-    {0x214, "GPUREG_ATTRIBBUFFER5_CONFIG2"},
-    {0x215, "GPUREG_ATTRIBBUFFER6_OFFSET"},
-    {0x216, "GPUREG_ATTRIBBUFFER6_CONFIG1"},
-    {0x217, "GPUREG_ATTRIBBUFFER6_CONFIG2"},
-    {0x218, "GPUREG_ATTRIBBUFFER7_OFFSET"},
-    {0x219, "GPUREG_ATTRIBBUFFER7_CONFIG1"},
-    {0x21A, "GPUREG_ATTRIBBUFFER7_CONFIG2"},
-    {0x21B, "GPUREG_ATTRIBBUFFER8_OFFSET"},
-    {0x21C, "GPUREG_ATTRIBBUFFER8_CONFIG1"},
-    {0x21D, "GPUREG_ATTRIBBUFFER8_CONFIG2"},
-    {0x21E, "GPUREG_ATTRIBBUFFER9_OFFSET"},
-    {0x21F, "GPUREG_ATTRIBBUFFER9_CONFIG1"},
-    {0x220, "GPUREG_ATTRIBBUFFER9_CONFIG2"},
-    {0x221, "GPUREG_ATTRIBBUFFER10_OFFSET"},
-    {0x222, "GPUREG_ATTRIBBUFFER10_CONFIG1"},
-    {0x223, "GPUREG_ATTRIBBUFFER10_CONFIG2"},
-    {0x224, "GPUREG_ATTRIBBUFFER11_OFFSET"},
-    {0x225, "GPUREG_ATTRIBBUFFER11_CONFIG1"},
-    {0x226, "GPUREG_ATTRIBBUFFER11_CONFIG2"},
-    {0x227, "GPUREG_INDEXBUFFER_CONFIG"},
-    {0x228, "GPUREG_NUMVERTICES"},
-    {0x229, "GPUREG_GEOSTAGE_CONFIG"},
-    {0x22A, "GPUREG_VERTEX_OFFSET"},
-
-    {0x22D, "GPUREG_POST_VERTEX_CACHE_NUM"},
-    {0x22E, "GPUREG_DRAWARRAYS"},
-    {0x22F, "GPUREG_DRAWELEMENTS"},
-
-    {0x231, "GPUREG_VTX_FUNC"},
-    {0x232, "GPUREG_FIXEDATTRIB_INDEX"},
-    {0x233, "GPUREG_FIXEDATTRIB_DATA0"},
-    {0x234, "GPUREG_FIXEDATTRIB_DATA1"},
-    {0x235, "GPUREG_FIXEDATTRIB_DATA2"},
-
-    {0x238, "GPUREG_CMDBUF_SIZE0"},
-    {0x239, "GPUREG_CMDBUF_SIZE1"},
-    {0x23A, "GPUREG_CMDBUF_ADDR0"},
-    {0x23B, "GPUREG_CMDBUF_ADDR1"},
-    {0x23C, "GPUREG_CMDBUF_JUMP0"},
-    {0x23D, "GPUREG_CMDBUF_JUMP1"},
-
-    {0x242, "GPUREG_VSH_NUM_ATTR"},
-
-    {0x244, "GPUREG_VSH_COM_MODE"},
-    {0x245, "GPUREG_START_DRAW_FUNC0"},
-
-    {0x24A, "GPUREG_VSH_OUTMAP_TOTAL1"},
-
-    {0x251, "GPUREG_VSH_OUTMAP_TOTAL2"},
-    {0x252, "GPUREG_GSH_MISC0"},
-    {0x253, "GPUREG_GEOSTAGE_CONFIG2"},
-    {0x254, "GPUREG_GSH_MISC1"},
-
-    {0x25E, "GPUREG_PRIMITIVE_CONFIG"},
-    {0x25F, "GPUREG_RESTART_PRIMITIVE"},
-
-    {0x280, "GPUREG_GSH_BOOLUNIFORM"},
-    {0x281, "GPUREG_GSH_INTUNIFORM_I0"},
-    {0x282, "GPUREG_GSH_INTUNIFORM_I1"},
-    {0x283, "GPUREG_GSH_INTUNIFORM_I2"},
-    {0x284, "GPUREG_GSH_INTUNIFORM_I3"},
-
-    {0x289, "GPUREG_GSH_INPUTBUFFER_CONFIG"},
-    {0x28A, "GPUREG_GSH_ENTRYPOINT"},
-    {0x28B, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_LOW"},
-    {0x28C, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_HIGH"},
-    {0x28D, "GPUREG_GSH_OUTMAP_MASK"},
-
-    {0x28F, "GPUREG_GSH_CODETRANSFER_END"},
-    {0x290, "GPUREG_GSH_FLOATUNIFORM_INDEX"},
-    {0x291, "GPUREG_GSH_FLOATUNIFORM_DATA0"},
-    {0x292, "GPUREG_GSH_FLOATUNIFORM_DATA1"},
-    {0x293, "GPUREG_GSH_FLOATUNIFORM_DATA2"},
-    {0x294, "GPUREG_GSH_FLOATUNIFORM_DATA3"},
-    {0x295, "GPUREG_GSH_FLOATUNIFORM_DATA4"},
-    {0x296, "GPUREG_GSH_FLOATUNIFORM_DATA5"},
-    {0x297, "GPUREG_GSH_FLOATUNIFORM_DATA6"},
-    {0x298, "GPUREG_GSH_FLOATUNIFORM_DATA7"},
-
-    {0x29B, "GPUREG_GSH_CODETRANSFER_INDEX"},
-    {0x29C, "GPUREG_GSH_CODETRANSFER_DATA0"},
-    {0x29D, "GPUREG_GSH_CODETRANSFER_DATA1"},
-    {0x29E, "GPUREG_GSH_CODETRANSFER_DATA2"},
-    {0x29F, "GPUREG_GSH_CODETRANSFER_DATA3"},
-    {0x2A0, "GPUREG_GSH_CODETRANSFER_DATA4"},
-    {0x2A1, "GPUREG_GSH_CODETRANSFER_DATA5"},
-    {0x2A2, "GPUREG_GSH_CODETRANSFER_DATA6"},
-    {0x2A3, "GPUREG_GSH_CODETRANSFER_DATA7"},
-
-    {0x2A5, "GPUREG_GSH_OPDESCS_INDEX"},
-    {0x2A6, "GPUREG_GSH_OPDESCS_DATA0"},
-    {0x2A7, "GPUREG_GSH_OPDESCS_DATA1"},
-    {0x2A8, "GPUREG_GSH_OPDESCS_DATA2"},
-    {0x2A9, "GPUREG_GSH_OPDESCS_DATA3"},
-    {0x2AA, "GPUREG_GSH_OPDESCS_DATA4"},
-    {0x2AB, "GPUREG_GSH_OPDESCS_DATA5"},
-    {0x2AC, "GPUREG_GSH_OPDESCS_DATA6"},
-    {0x2AD, "GPUREG_GSH_OPDESCS_DATA7"},
-
-    {0x2B0, "GPUREG_VSH_BOOLUNIFORM"},
-    {0x2B1, "GPUREG_VSH_INTUNIFORM_I0"},
-    {0x2B2, "GPUREG_VSH_INTUNIFORM_I1"},
-    {0x2B3, "GPUREG_VSH_INTUNIFORM_I2"},
-    {0x2B4, "GPUREG_VSH_INTUNIFORM_I3"},
-
-    {0x2B9, "GPUREG_VSH_INPUTBUFFER_CONFIG"},
-    {0x2BA, "GPUREG_VSH_ENTRYPOINT"},
-    {0x2BB, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_LOW"},
-    {0x2BC, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_HIGH"},
-    {0x2BD, "GPUREG_VSH_OUTMAP_MASK"},
-
-    {0x2BF, "GPUREG_VSH_CODETRANSFER_END"},
-    {0x2C0, "GPUREG_VSH_FLOATUNIFORM_INDEX"},
-    {0x2C1, "GPUREG_VSH_FLOATUNIFORM_DATA0"},
-    {0x2C2, "GPUREG_VSH_FLOATUNIFORM_DATA1"},
-    {0x2C3, "GPUREG_VSH_FLOATUNIFORM_DATA2"},
-    {0x2C4, "GPUREG_VSH_FLOATUNIFORM_DATA3"},
-    {0x2C5, "GPUREG_VSH_FLOATUNIFORM_DATA4"},
-    {0x2C6, "GPUREG_VSH_FLOATUNIFORM_DATA5"},
-    {0x2C7, "GPUREG_VSH_FLOATUNIFORM_DATA6"},
-    {0x2C8, "GPUREG_VSH_FLOATUNIFORM_DATA7"},
-
-    {0x2CB, "GPUREG_VSH_CODETRANSFER_INDEX"},
-    {0x2CC, "GPUREG_VSH_CODETRANSFER_DATA0"},
-    {0x2CD, "GPUREG_VSH_CODETRANSFER_DATA1"},
-    {0x2CE, "GPUREG_VSH_CODETRANSFER_DATA2"},
-    {0x2CF, "GPUREG_VSH_CODETRANSFER_DATA3"},
-    {0x2D0, "GPUREG_VSH_CODETRANSFER_DATA4"},
-    {0x2D1, "GPUREG_VSH_CODETRANSFER_DATA5"},
-    {0x2D2, "GPUREG_VSH_CODETRANSFER_DATA6"},
-    {0x2D3, "GPUREG_VSH_CODETRANSFER_DATA7"},
-
-    {0x2D5, "GPUREG_VSH_OPDESCS_INDEX"},
-    {0x2D6, "GPUREG_VSH_OPDESCS_DATA0"},
-    {0x2D7, "GPUREG_VSH_OPDESCS_DATA1"},
-    {0x2D8, "GPUREG_VSH_OPDESCS_DATA2"},
-    {0x2D9, "GPUREG_VSH_OPDESCS_DATA3"},
-    {0x2DA, "GPUREG_VSH_OPDESCS_DATA4"},
-    {0x2DB, "GPUREG_VSH_OPDESCS_DATA5"},
-    {0x2DC, "GPUREG_VSH_OPDESCS_DATA6"},
-    {0x2DD, "GPUREG_VSH_OPDESCS_DATA7"},
-};
-
-std::string Regs::GetCommandName(int index) {
-    static std::unordered_map<u32, const char*> map;
-
-    if (map.empty()) {
-        map.insert(std::begin(register_names), std::end(register_names));
-    }
-
-    // Return empty string if no match is found
-    auto it = map.find(index);
-    if (it != map.end()) {
-        return it->second;
-    } else {
-        return std::string();
-    }
-}
-
 void Init() {
     g_state.Reset();
 }
@@ -513,6 +30,6 @@ void State::Reset() {
     Zero(gs);
     Zero(cmd_list);
     Zero(immediate);
-    primitive_assembler.Reconfigure(Regs::TriangleTopology::List);
+    primitive_assembler.Reconfigure(PipelineRegs::TriangleTopology::List);
 }
 }
diff --git a/src/video_core/pica.h b/src/video_core/pica.h
index 731540b999..dc8aa66707 100644
--- a/src/video_core/pica.h
+++ b/src/video_core/pica.h
@@ -4,1400 +4,9 @@
 
 #pragma once
 
-#include <array>
-#include <cstddef>
-#include <string>
-
-#ifndef _MSC_VER
-#include <type_traits> // for std::enable_if
-#endif
-
-#include "common/assert.h"
-#include "common/bit_field.h"
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "common/vector_math.h"
-
+#include "video_core/regs_texturing.h"
 namespace Pica {
 
-// Returns index corresponding to the Regs member labeled by field_name
-// TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions
-//       when used with array elements (e.g. PICA_REG_INDEX(vs_uniform_setup.set_value[1])).
-//       For details cf.
-//       https://connect.microsoft.com/VisualStudio/feedback/details/209229/offsetof-does-not-produce-a-constant-expression-for-array-members
-//       Hopefully, this will be fixed sometime in the future.
-//       For lack of better alternatives, we currently hardcode the offsets when constant
-//       expressions are needed via PICA_REG_INDEX_WORKAROUND (on sane compilers, static_asserts
-//       will then make sure the offsets indeed match the automatically calculated ones).
-#define PICA_REG_INDEX(field_name) (offsetof(Pica::Regs, field_name) / sizeof(u32))
-#if defined(_MSC_VER)
-#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) (backup_workaround_index)
-#else
-// NOTE: Yeah, hacking in a static_assert here just to workaround the lacking MSVC compiler
-//       really is this annoying. This macro just forwards its first argument to PICA_REG_INDEX
-//       and then performs a (no-op) cast to size_t iff the second argument matches the expected
-//       field offset. Otherwise, the compiler will fail to compile this code.
-#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index)                             \
-    ((typename std::enable_if<backup_workaround_index == PICA_REG_INDEX(field_name),               \
-                              size_t>::type)PICA_REG_INDEX(field_name))
-#endif // _MSC_VER
-
-struct Regs {
-
-    INSERT_PADDING_WORDS(0x10);
-
-    u32 trigger_irq;
-
-    INSERT_PADDING_WORDS(0x2f);
-
-    enum class CullMode : u32 {
-        // Select which polygons are considered to be "frontfacing".
-        KeepAll = 0,
-        KeepClockWise = 1,
-        KeepCounterClockWise = 2,
-        // TODO: What does the third value imply?
-    };
-
-    union {
-        BitField<0, 2, CullMode> cull_mode;
-    };
-
-    BitField<0, 24, u32> viewport_size_x;
-
-    INSERT_PADDING_WORDS(0x1);
-
-    BitField<0, 24, u32> viewport_size_y;
-
-    INSERT_PADDING_WORDS(0x9);
-
-    BitField<0, 24, u32> viewport_depth_range;      // float24
-    BitField<0, 24, u32> viewport_depth_near_plane; // float24
-
-    BitField<0, 3, u32> vs_output_total;
-
-    union VSOutputAttributes {
-        // Maps components of output vertex attributes to semantics
-        enum Semantic : u32 {
-            POSITION_X = 0,
-            POSITION_Y = 1,
-            POSITION_Z = 2,
-            POSITION_W = 3,
-
-            QUATERNION_X = 4,
-            QUATERNION_Y = 5,
-            QUATERNION_Z = 6,
-            QUATERNION_W = 7,
-
-            COLOR_R = 8,
-            COLOR_G = 9,
-            COLOR_B = 10,
-            COLOR_A = 11,
-
-            TEXCOORD0_U = 12,
-            TEXCOORD0_V = 13,
-            TEXCOORD1_U = 14,
-            TEXCOORD1_V = 15,
-
-            TEXCOORD0_W = 16,
-
-            VIEW_X = 18,
-            VIEW_Y = 19,
-            VIEW_Z = 20,
-
-            TEXCOORD2_U = 22,
-            TEXCOORD2_V = 23,
-
-            INVALID = 31,
-        };
-
-        BitField<0, 5, Semantic> map_x;
-        BitField<8, 5, Semantic> map_y;
-        BitField<16, 5, Semantic> map_z;
-        BitField<24, 5, Semantic> map_w;
-    } vs_output_attributes[7];
-
-    INSERT_PADDING_WORDS(0xe);
-
-    enum class ScissorMode : u32 {
-        Disabled = 0,
-        Exclude = 1, // Exclude pixels inside the scissor box
-
-        Include = 3 // Exclude pixels outside the scissor box
-    };
-
-    struct {
-        BitField<0, 2, ScissorMode> mode;
-
-        union {
-            BitField<0, 16, u32> x1;
-            BitField<16, 16, u32> y1;
-        };
-
-        union {
-            BitField<0, 16, u32> x2;
-            BitField<16, 16, u32> y2;
-        };
-    } scissor_test;
-
-    union {
-        BitField<0, 10, s32> x;
-        BitField<16, 10, s32> y;
-    } viewport_corner;
-
-    INSERT_PADDING_WORDS(0x1);
-
-    // TODO: early depth
-    INSERT_PADDING_WORDS(0x1);
-
-    INSERT_PADDING_WORDS(0x2);
-
-    enum DepthBuffering : u32 {
-        WBuffering = 0,
-        ZBuffering = 1,
-    };
-    BitField<0, 1, DepthBuffering> depthmap_enable;
-
-    INSERT_PADDING_WORDS(0x12);
-
-    struct TextureConfig {
-        enum TextureType : u32 {
-            Texture2D = 0,
-            TextureCube = 1,
-            Shadow2D = 2,
-            Projection2D = 3,
-            ShadowCube = 4,
-            Disabled = 5,
-        };
-
-        enum WrapMode : u32 {
-            ClampToEdge = 0,
-            ClampToBorder = 1,
-            Repeat = 2,
-            MirroredRepeat = 3,
-        };
-
-        enum TextureFilter : u32 {
-            Nearest = 0,
-            Linear = 1,
-        };
-
-        union {
-            u32 raw;
-            BitField<0, 8, u32> r;
-            BitField<8, 8, u32> g;
-            BitField<16, 8, u32> b;
-            BitField<24, 8, u32> a;
-        } border_color;
-
-        union {
-            BitField<0, 16, u32> height;
-            BitField<16, 16, u32> width;
-        };
-
-        union {
-            BitField<1, 1, TextureFilter> mag_filter;
-            BitField<2, 1, TextureFilter> min_filter;
-            BitField<8, 2, WrapMode> wrap_t;
-            BitField<12, 2, WrapMode> wrap_s;
-            BitField<28, 2, TextureType>
-                type; ///< @note Only valid for texture 0 according to 3DBrew.
-        };
-
-        INSERT_PADDING_WORDS(0x1);
-
-        u32 address;
-
-        u32 GetPhysicalAddress() const {
-            return DecodeAddressRegister(address);
-        }
-
-        // texture1 and texture2 store the texture format directly after the address
-        // whereas texture0 inserts some additional flags inbetween.
-        // Hence, we store the format separately so that all other parameters can be described
-        // in a single structure.
-    };
-
-    enum class TextureFormat : u32 {
-        RGBA8 = 0,
-        RGB8 = 1,
-        RGB5A1 = 2,
-        RGB565 = 3,
-        RGBA4 = 4,
-        IA8 = 5,
-        RG8 = 6, ///< @note Also called HILO8 in 3DBrew.
-        I8 = 7,
-        A8 = 8,
-        IA4 = 9,
-        I4 = 10,
-        A4 = 11,
-        ETC1 = 12,   // compressed
-        ETC1A4 = 13, // compressed
-    };
-
-    enum class LogicOp : u32 {
-        Clear = 0,
-        And = 1,
-        AndReverse = 2,
-        Copy = 3,
-        Set = 4,
-        CopyInverted = 5,
-        NoOp = 6,
-        Invert = 7,
-        Nand = 8,
-        Or = 9,
-        Nor = 10,
-        Xor = 11,
-        Equiv = 12,
-        AndInverted = 13,
-        OrReverse = 14,
-        OrInverted = 15,
-    };
-
-    static unsigned NibblesPerPixel(TextureFormat format) {
-        switch (format) {
-        case TextureFormat::RGBA8:
-            return 8;
-
-        case TextureFormat::RGB8:
-            return 6;
-
-        case TextureFormat::RGB5A1:
-        case TextureFormat::RGB565:
-        case TextureFormat::RGBA4:
-        case TextureFormat::IA8:
-        case TextureFormat::RG8:
-            return 4;
-
-        case TextureFormat::I4:
-        case TextureFormat::A4:
-            return 1;
-
-        case TextureFormat::I8:
-        case TextureFormat::A8:
-        case TextureFormat::IA4:
-            return 2;
-
-        default: // placeholder for yet unknown formats
-            UNIMPLEMENTED();
-            return 0;
-        }
-    }
-
-    union {
-        BitField<0, 1, u32> texture0_enable;
-        BitField<1, 1, u32> texture1_enable;
-        BitField<2, 1, u32> texture2_enable;
-    };
-    TextureConfig texture0;
-    INSERT_PADDING_WORDS(0x8);
-    BitField<0, 4, TextureFormat> texture0_format;
-    BitField<0, 1, u32> fragment_lighting_enable;
-    INSERT_PADDING_WORDS(0x1);
-    TextureConfig texture1;
-    BitField<0, 4, TextureFormat> texture1_format;
-    INSERT_PADDING_WORDS(0x2);
-    TextureConfig texture2;
-    BitField<0, 4, TextureFormat> texture2_format;
-    INSERT_PADDING_WORDS(0x21);
-
-    struct FullTextureConfig {
-        const bool enabled;
-        const TextureConfig config;
-        const TextureFormat format;
-    };
-    const std::array<FullTextureConfig, 3> GetTextures() const {
-        return {{
-            {texture0_enable.ToBool(), texture0, texture0_format},
-            {texture1_enable.ToBool(), texture1, texture1_format},
-            {texture2_enable.ToBool(), texture2, texture2_format},
-        }};
-    }
-
-    // 0xc0-0xff: Texture Combiner (akin to glTexEnv)
-    struct TevStageConfig {
-        enum class Source : u32 {
-            PrimaryColor = 0x0,
-            PrimaryFragmentColor = 0x1,
-            SecondaryFragmentColor = 0x2,
-
-            Texture0 = 0x3,
-            Texture1 = 0x4,
-            Texture2 = 0x5,
-            Texture3 = 0x6,
-
-            PreviousBuffer = 0xd,
-            Constant = 0xe,
-            Previous = 0xf,
-        };
-
-        enum class ColorModifier : u32 {
-            SourceColor = 0x0,
-            OneMinusSourceColor = 0x1,
-            SourceAlpha = 0x2,
-            OneMinusSourceAlpha = 0x3,
-            SourceRed = 0x4,
-            OneMinusSourceRed = 0x5,
-
-            SourceGreen = 0x8,
-            OneMinusSourceGreen = 0x9,
-
-            SourceBlue = 0xc,
-            OneMinusSourceBlue = 0xd,
-        };
-
-        enum class AlphaModifier : u32 {
-            SourceAlpha = 0x0,
-            OneMinusSourceAlpha = 0x1,
-            SourceRed = 0x2,
-            OneMinusSourceRed = 0x3,
-            SourceGreen = 0x4,
-            OneMinusSourceGreen = 0x5,
-            SourceBlue = 0x6,
-            OneMinusSourceBlue = 0x7,
-        };
-
-        enum class Operation : u32 {
-            Replace = 0,
-            Modulate = 1,
-            Add = 2,
-            AddSigned = 3,
-            Lerp = 4,
-            Subtract = 5,
-            Dot3_RGB = 6,
-
-            MultiplyThenAdd = 8,
-            AddThenMultiply = 9,
-        };
-
-        union {
-            u32 sources_raw;
-            BitField<0, 4, Source> color_source1;
-            BitField<4, 4, Source> color_source2;
-            BitField<8, 4, Source> color_source3;
-            BitField<16, 4, Source> alpha_source1;
-            BitField<20, 4, Source> alpha_source2;
-            BitField<24, 4, Source> alpha_source3;
-        };
-
-        union {
-            u32 modifiers_raw;
-            BitField<0, 4, ColorModifier> color_modifier1;
-            BitField<4, 4, ColorModifier> color_modifier2;
-            BitField<8, 4, ColorModifier> color_modifier3;
-            BitField<12, 3, AlphaModifier> alpha_modifier1;
-            BitField<16, 3, AlphaModifier> alpha_modifier2;
-            BitField<20, 3, AlphaModifier> alpha_modifier3;
-        };
-
-        union {
-            u32 ops_raw;
-            BitField<0, 4, Operation> color_op;
-            BitField<16, 4, Operation> alpha_op;
-        };
-
-        union {
-            u32 const_color;
-            BitField<0, 8, u32> const_r;
-            BitField<8, 8, u32> const_g;
-            BitField<16, 8, u32> const_b;
-            BitField<24, 8, u32> const_a;
-        };
-
-        union {
-            u32 scales_raw;
-            BitField<0, 2, u32> color_scale;
-            BitField<16, 2, u32> alpha_scale;
-        };
-
-        inline unsigned GetColorMultiplier() const {
-            return (color_scale < 3) ? (1 << color_scale) : 1;
-        }
-
-        inline unsigned GetAlphaMultiplier() const {
-            return (alpha_scale < 3) ? (1 << alpha_scale) : 1;
-        }
-    };
-
-    TevStageConfig tev_stage0;
-    INSERT_PADDING_WORDS(0x3);
-    TevStageConfig tev_stage1;
-    INSERT_PADDING_WORDS(0x3);
-    TevStageConfig tev_stage2;
-    INSERT_PADDING_WORDS(0x3);
-    TevStageConfig tev_stage3;
-    INSERT_PADDING_WORDS(0x3);
-
-    enum class FogMode : u32 {
-        None = 0,
-        Fog = 5,
-        Gas = 7,
-    };
-
-    union {
-        BitField<0, 3, FogMode> fog_mode;
-        BitField<16, 1, u32> fog_flip;
-
-        union {
-            // Tev stages 0-3 write their output to the combiner buffer if the corresponding bit in
-            // these masks are set
-            BitField<8, 4, u32> update_mask_rgb;
-            BitField<12, 4, u32> update_mask_a;
-
-            bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
-                return (stage_index < 4) && (update_mask_rgb & (1 << stage_index));
-            }
-
-            bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const {
-                return (stage_index < 4) && (update_mask_a & (1 << stage_index));
-            }
-        } tev_combiner_buffer_input;
-    };
-
-    union {
-        u32 raw;
-        BitField<0, 8, u32> r;
-        BitField<8, 8, u32> g;
-        BitField<16, 8, u32> b;
-    } fog_color;
-
-    INSERT_PADDING_WORDS(0x4);
-
-    BitField<0, 16, u32> fog_lut_offset;
-
-    INSERT_PADDING_WORDS(0x1);
-
-    u32 fog_lut_data[8];
-
-    TevStageConfig tev_stage4;
-    INSERT_PADDING_WORDS(0x3);
-    TevStageConfig tev_stage5;
-
-    union {
-        u32 raw;
-        BitField<0, 8, u32> r;
-        BitField<8, 8, u32> g;
-        BitField<16, 8, u32> b;
-        BitField<24, 8, u32> a;
-    } tev_combiner_buffer_color;
-
-    INSERT_PADDING_WORDS(0x2);
-
-    const std::array<Regs::TevStageConfig, 6> GetTevStages() const {
-        return {{tev_stage0, tev_stage1, tev_stage2, tev_stage3, tev_stage4, tev_stage5}};
-    };
-
-    enum class BlendEquation : u32 {
-        Add = 0,
-        Subtract = 1,
-        ReverseSubtract = 2,
-        Min = 3,
-        Max = 4,
-    };
-
-    enum class BlendFactor : u32 {
-        Zero = 0,
-        One = 1,
-        SourceColor = 2,
-        OneMinusSourceColor = 3,
-        DestColor = 4,
-        OneMinusDestColor = 5,
-        SourceAlpha = 6,
-        OneMinusSourceAlpha = 7,
-        DestAlpha = 8,
-        OneMinusDestAlpha = 9,
-        ConstantColor = 10,
-        OneMinusConstantColor = 11,
-        ConstantAlpha = 12,
-        OneMinusConstantAlpha = 13,
-        SourceAlphaSaturate = 14,
-    };
-
-    enum class CompareFunc : u32 {
-        Never = 0,
-        Always = 1,
-        Equal = 2,
-        NotEqual = 3,
-        LessThan = 4,
-        LessThanOrEqual = 5,
-        GreaterThan = 6,
-        GreaterThanOrEqual = 7,
-    };
-
-    enum class StencilAction : u32 {
-        Keep = 0,
-        Zero = 1,
-        Replace = 2,
-        Increment = 3,
-        Decrement = 4,
-        Invert = 5,
-        IncrementWrap = 6,
-        DecrementWrap = 7,
-    };
-
-    struct {
-        union {
-            // If false, logic blending is used
-            BitField<8, 1, u32> alphablend_enable;
-        };
-
-        union {
-            BitField<0, 8, BlendEquation> blend_equation_rgb;
-            BitField<8, 8, BlendEquation> blend_equation_a;
-
-            BitField<16, 4, BlendFactor> factor_source_rgb;
-            BitField<20, 4, BlendFactor> factor_dest_rgb;
-
-            BitField<24, 4, BlendFactor> factor_source_a;
-            BitField<28, 4, BlendFactor> factor_dest_a;
-        } alpha_blending;
-
-        union {
-            BitField<0, 4, LogicOp> logic_op;
-        };
-
-        union {
-            u32 raw;
-            BitField<0, 8, u32> r;
-            BitField<8, 8, u32> g;
-            BitField<16, 8, u32> b;
-            BitField<24, 8, u32> a;
-        } blend_const;
-
-        union {
-            BitField<0, 1, u32> enable;
-            BitField<4, 3, CompareFunc> func;
-            BitField<8, 8, u32> ref;
-        } alpha_test;
-
-        struct {
-            union {
-                // Raw value of this register
-                u32 raw_func;
-
-                // If true, enable stencil testing
-                BitField<0, 1, u32> enable;
-
-                // Comparison operation for stencil testing
-                BitField<4, 3, CompareFunc> func;
-
-                // Mask used to control writing to the stencil buffer
-                BitField<8, 8, u32> write_mask;
-
-                // Value to compare against for stencil testing
-                BitField<16, 8, u32> reference_value;
-
-                // Mask to apply on stencil test inputs
-                BitField<24, 8, u32> input_mask;
-            };
-
-            union {
-                // Raw value of this register
-                u32 raw_op;
-
-                // Action to perform when the stencil test fails
-                BitField<0, 3, StencilAction> action_stencil_fail;
-
-                // Action to perform when stencil testing passed but depth testing fails
-                BitField<4, 3, StencilAction> action_depth_fail;
-
-                // Action to perform when both stencil and depth testing pass
-                BitField<8, 3, StencilAction> action_depth_pass;
-            };
-        } stencil_test;
-
-        union {
-            BitField<0, 1, u32> depth_test_enable;
-            BitField<4, 3, CompareFunc> depth_test_func;
-            BitField<8, 1, u32> red_enable;
-            BitField<9, 1, u32> green_enable;
-            BitField<10, 1, u32> blue_enable;
-            BitField<11, 1, u32> alpha_enable;
-            BitField<12, 1, u32> depth_write_enable;
-        };
-
-        INSERT_PADDING_WORDS(0x8);
-    } output_merger;
-
-    // Components are laid out in reverse byte order, most significant bits first.
-    enum class ColorFormat : u32 {
-        RGBA8 = 0,
-        RGB8 = 1,
-        RGB5A1 = 2,
-        RGB565 = 3,
-        RGBA4 = 4,
-    };
-
-    enum class DepthFormat : u32 {
-        D16 = 0,
-        D24 = 2,
-        D24S8 = 3,
-    };
-
-    // Returns the number of bytes in the specified color format
-    static unsigned BytesPerColorPixel(ColorFormat format) {
-        switch (format) {
-        case ColorFormat::RGBA8:
-            return 4;
-        case ColorFormat::RGB8:
-            return 3;
-        case ColorFormat::RGB5A1:
-        case ColorFormat::RGB565:
-        case ColorFormat::RGBA4:
-            return 2;
-        default:
-            LOG_CRITICAL(HW_GPU, "Unknown color format %u", format);
-            UNIMPLEMENTED();
-        }
-    }
-
-    struct FramebufferConfig {
-        INSERT_PADDING_WORDS(0x3);
-
-        union {
-            BitField<0, 4, u32> allow_color_write; // 0 = disable, else enable
-        };
-
-        INSERT_PADDING_WORDS(0x1);
-
-        union {
-            BitField<0, 2, u32> allow_depth_stencil_write; // 0 = disable, else enable
-        };
-
-        DepthFormat depth_format; // TODO: Should be a BitField!
-        BitField<16, 3, ColorFormat> color_format;
-
-        INSERT_PADDING_WORDS(0x4);
-
-        u32 depth_buffer_address;
-        u32 color_buffer_address;
-
-        union {
-            // Apparently, the framebuffer width is stored as expected,
-            // while the height is stored as the actual height minus one.
-            // Hence, don't access these fields directly but use the accessors
-            // GetWidth() and GetHeight() instead.
-            BitField<0, 11, u32> width;
-            BitField<12, 10, u32> height;
-        };
-
-        INSERT_PADDING_WORDS(0x1);
-
-        inline u32 GetColorBufferPhysicalAddress() const {
-            return DecodeAddressRegister(color_buffer_address);
-        }
-        inline u32 GetDepthBufferPhysicalAddress() const {
-            return DecodeAddressRegister(depth_buffer_address);
-        }
-
-        inline u32 GetWidth() const {
-            return width;
-        }
-
-        inline u32 GetHeight() const {
-            return height + 1;
-        }
-    } framebuffer;
-
-    // Returns the number of bytes in the specified depth format
-    static u32 BytesPerDepthPixel(DepthFormat format) {
-        switch (format) {
-        case DepthFormat::D16:
-            return 2;
-        case DepthFormat::D24:
-            return 3;
-        case DepthFormat::D24S8:
-            return 4;
-        default:
-            LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format);
-            UNIMPLEMENTED();
-        }
-    }
-
-    // Returns the number of bits per depth component of the specified depth format
-    static u32 DepthBitsPerPixel(DepthFormat format) {
-        switch (format) {
-        case DepthFormat::D16:
-            return 16;
-        case DepthFormat::D24:
-        case DepthFormat::D24S8:
-            return 24;
-        default:
-            LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format);
-            UNIMPLEMENTED();
-        }
-    }
-
-    INSERT_PADDING_WORDS(0x20);
-
-    enum class LightingSampler {
-        Distribution0 = 0,
-        Distribution1 = 1,
-        Fresnel = 3,
-        ReflectBlue = 4,
-        ReflectGreen = 5,
-        ReflectRed = 6,
-        SpotlightAttenuation = 8,
-        DistanceAttenuation = 16,
-    };
-
-    /**
-     * Pica fragment lighting supports using different LUTs for each lighting component:
-     * Reflectance R, G, and B channels, distribution function for specular components 0 and 1,
-     * fresnel factor, and spotlight attenuation. Furthermore, which LUTs are used for each channel
-     * (or whether a channel is enabled at all) is specified by various pre-defined lighting
-     * configurations. With configurations that require more LUTs, more cycles are required on HW to
-     * perform lighting computations.
-     */
-    enum class LightingConfig {
-        Config0 = 0, ///< Reflect Red, Distribution 0, Spotlight
-        Config1 = 1, ///< Reflect Red, Fresnel, Spotlight
-        Config2 = 2, ///< Reflect Red, Distribution 0/1
-        Config3 = 3, ///< Distribution 0/1, Fresnel
-        Config4 = 4, ///< Reflect Red/Green/Blue, Distribution 0/1, Spotlight
-        Config5 = 5, ///< Reflect Red/Green/Blue, Distribution 0, Fresnel, Spotlight
-        Config6 = 6, ///< Reflect Red, Distribution 0/1, Fresnel, Spotlight
-        Config7 = 8, ///< Reflect Red/Green/Blue, Distribution 0/1, Fresnel, Spotlight
-                     ///< NOTE: '8' is intentional, '7' does not appear to be a valid configuration
-    };
-
-    /// Selects which lighting components are affected by fresnel
-    enum class LightingFresnelSelector {
-        None = 0,           ///< Fresnel is disabled
-        PrimaryAlpha = 1,   ///< Primary (diffuse) lighting alpha is affected by fresnel
-        SecondaryAlpha = 2, ///< Secondary (specular) lighting alpha is affected by fresnel
-        Both =
-            PrimaryAlpha |
-            SecondaryAlpha, ///< Both primary and secondary lighting alphas are affected by fresnel
-    };
-
-    /// Factor used to scale the output of a lighting LUT
-    enum class LightingScale {
-        Scale1 = 0,   ///< Scale is 1x
-        Scale2 = 1,   ///< Scale is 2x
-        Scale4 = 2,   ///< Scale is 4x
-        Scale8 = 3,   ///< Scale is 8x
-        Scale1_4 = 6, ///< Scale is 0.25x
-        Scale1_2 = 7, ///< Scale is 0.5x
-    };
-
-    enum class LightingLutInput {
-        NH = 0, // Cosine of the angle between the normal and half-angle vectors
-        VH = 1, // Cosine of the angle between the view and half-angle vectors
-        NV = 2, // Cosine of the angle between the normal and the view vector
-        LN = 3, // Cosine of the angle between the light and the normal vectors
-    };
-
-    enum class LightingBumpMode : u32 {
-        None = 0,
-        NormalMap = 1,
-        TangentMap = 2,
-    };
-
-    union LightColor {
-        BitField<0, 10, u32> b;
-        BitField<10, 10, u32> g;
-        BitField<20, 10, u32> r;
-
-        Math::Vec3f ToVec3f() const {
-            // These fields are 10 bits wide, however 255 corresponds to 1.0f for each color
-            // component
-            return Math::MakeVec((f32)r / 255.f, (f32)g / 255.f, (f32)b / 255.f);
-        }
-    };
-
-    /// Returns true if the specified lighting sampler is supported by the current Pica lighting
-    /// configuration
-    static bool IsLightingSamplerSupported(LightingConfig config, LightingSampler sampler) {
-        switch (sampler) {
-        case LightingSampler::Distribution0:
-            return (config != LightingConfig::Config1);
-
-        case LightingSampler::Distribution1:
-            return (config != LightingConfig::Config0) && (config != LightingConfig::Config1) &&
-                   (config != LightingConfig::Config5);
-
-        case LightingSampler::Fresnel:
-            return (config != LightingConfig::Config0) && (config != LightingConfig::Config2) &&
-                   (config != LightingConfig::Config4);
-
-        case LightingSampler::ReflectRed:
-            return (config != LightingConfig::Config3);
-
-        case LightingSampler::ReflectGreen:
-        case LightingSampler::ReflectBlue:
-            return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) ||
-                   (config == LightingConfig::Config7);
-        default:
-            UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached "
-                            "unreachable section, sampler should be one "
-                            "of Distribution0, Distribution1, Fresnel, "
-                            "ReflectRed, ReflectGreen or ReflectBlue, instead "
-                            "got %i",
-                            static_cast<int>(config));
-        }
-    }
-
-    struct {
-        struct LightSrc {
-            LightColor specular_0; // material.specular_0 * light.specular_0
-            LightColor specular_1; // material.specular_1 * light.specular_1
-            LightColor diffuse;    // material.diffuse * light.diffuse
-            LightColor ambient;    // material.ambient * light.ambient
-
-            // Encoded as 16-bit floating point
-            union {
-                BitField<0, 16, u32> x;
-                BitField<16, 16, u32> y;
-            };
-            union {
-                BitField<0, 16, u32> z;
-            };
-
-            INSERT_PADDING_WORDS(0x3);
-
-            union {
-                BitField<0, 1, u32> directional;
-                BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0
-            } config;
-
-            BitField<0, 20, u32> dist_atten_bias;
-            BitField<0, 20, u32> dist_atten_scale;
-
-            INSERT_PADDING_WORDS(0x4);
-        };
-        static_assert(sizeof(LightSrc) == 0x10 * sizeof(u32),
-                      "LightSrc structure must be 0x10 words");
-
-        LightSrc light[8];
-        LightColor global_ambient; // Emission + (material.ambient * lighting.ambient)
-        INSERT_PADDING_WORDS(0x1);
-        BitField<0, 3, u32> max_light_index; // Number of enabled lights - 1
-
-        union {
-            BitField<2, 2, LightingFresnelSelector> fresnel_selector;
-            BitField<4, 4, LightingConfig> config;
-            BitField<22, 2, u32> bump_selector; // 0: Texture 0, 1: Texture 1, 2: Texture 2
-            BitField<27, 1, u32> clamp_highlights;
-            BitField<28, 2, LightingBumpMode> bump_mode;
-            BitField<30, 1, u32> disable_bump_renorm;
-        } config0;
-
-        union {
-            BitField<16, 1, u32> disable_lut_d0;
-            BitField<17, 1, u32> disable_lut_d1;
-            BitField<19, 1, u32> disable_lut_fr;
-            BitField<20, 1, u32> disable_lut_rr;
-            BitField<21, 1, u32> disable_lut_rg;
-            BitField<22, 1, u32> disable_lut_rb;
-
-            // Each bit specifies whether distance attenuation should be applied for the
-            // corresponding light
-
-            BitField<24, 1, u32> disable_dist_atten_light_0;
-            BitField<25, 1, u32> disable_dist_atten_light_1;
-            BitField<26, 1, u32> disable_dist_atten_light_2;
-            BitField<27, 1, u32> disable_dist_atten_light_3;
-            BitField<28, 1, u32> disable_dist_atten_light_4;
-            BitField<29, 1, u32> disable_dist_atten_light_5;
-            BitField<30, 1, u32> disable_dist_atten_light_6;
-            BitField<31, 1, u32> disable_dist_atten_light_7;
-        } config1;
-
-        bool IsDistAttenDisabled(unsigned index) const {
-            const unsigned disable[] = {
-                config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1,
-                config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3,
-                config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5,
-                config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7};
-            return disable[index] != 0;
-        }
-
-        union {
-            BitField<0, 8, u32> index; ///< Index at which to set data in the LUT
-            BitField<8, 5, u32> type;  ///< Type of LUT for which to set data
-        } lut_config;
-
-        BitField<0, 1, u32> disable;
-        INSERT_PADDING_WORDS(0x1);
-
-        // When data is written to any of these registers, it gets written to the lookup table of
-        // the selected type at the selected index, specified above in the `lut_config` register.
-        // With each write, `lut_config.index` is incremented. It does not matter which of these
-        // registers is written to, the behavior will be the same.
-        u32 lut_data[8];
-
-        // These are used to specify if absolute (abs) value should be used for each LUT index. When
-        // abs mode is disabled, LUT indexes are in the range of (-1.0, 1.0). Otherwise, they are in
-        // the range of (0.0, 1.0).
-        union {
-            BitField<1, 1, u32> disable_d0;
-            BitField<5, 1, u32> disable_d1;
-            BitField<9, 1, u32> disable_sp;
-            BitField<13, 1, u32> disable_fr;
-            BitField<17, 1, u32> disable_rb;
-            BitField<21, 1, u32> disable_rg;
-            BitField<25, 1, u32> disable_rr;
-        } abs_lut_input;
-
-        union {
-            BitField<0, 3, LightingLutInput> d0;
-            BitField<4, 3, LightingLutInput> d1;
-            BitField<8, 3, LightingLutInput> sp;
-            BitField<12, 3, LightingLutInput> fr;
-            BitField<16, 3, LightingLutInput> rb;
-            BitField<20, 3, LightingLutInput> rg;
-            BitField<24, 3, LightingLutInput> rr;
-        } lut_input;
-
-        union {
-            BitField<0, 3, LightingScale> d0;
-            BitField<4, 3, LightingScale> d1;
-            BitField<8, 3, LightingScale> sp;
-            BitField<12, 3, LightingScale> fr;
-            BitField<16, 3, LightingScale> rb;
-            BitField<20, 3, LightingScale> rg;
-            BitField<24, 3, LightingScale> rr;
-
-            static float GetScale(LightingScale scale) {
-                switch (scale) {
-                case LightingScale::Scale1:
-                    return 1.0f;
-                case LightingScale::Scale2:
-                    return 2.0f;
-                case LightingScale::Scale4:
-                    return 4.0f;
-                case LightingScale::Scale8:
-                    return 8.0f;
-                case LightingScale::Scale1_4:
-                    return 0.25f;
-                case LightingScale::Scale1_2:
-                    return 0.5f;
-                }
-                return 0.0f;
-            }
-        } lut_scale;
-
-        INSERT_PADDING_WORDS(0x6);
-
-        union {
-            // There are 8 light enable "slots", corresponding to the total number of lights
-            // supported by Pica. For N enabled lights (specified by register 0x1c2, or 'src_num'
-            // above), the first N slots below will be set to integers within the range of 0-7,
-            // corresponding to the actual light that is enabled for each slot.
-
-            BitField<0, 3, u32> slot_0;
-            BitField<4, 3, u32> slot_1;
-            BitField<8, 3, u32> slot_2;
-            BitField<12, 3, u32> slot_3;
-            BitField<16, 3, u32> slot_4;
-            BitField<20, 3, u32> slot_5;
-            BitField<24, 3, u32> slot_6;
-            BitField<28, 3, u32> slot_7;
-
-            unsigned GetNum(unsigned index) const {
-                const unsigned enable_slots[] = {slot_0, slot_1, slot_2, slot_3,
-                                                 slot_4, slot_5, slot_6, slot_7};
-                return enable_slots[index];
-            }
-        } light_enable;
-    } lighting;
-
-    INSERT_PADDING_WORDS(0x26);
-
-    enum class VertexAttributeFormat : u64 {
-        BYTE = 0,
-        UBYTE = 1,
-        SHORT = 2,
-        FLOAT = 3,
-    };
-
-    struct {
-        BitField<0, 29, u32> base_address;
-
-        u32 GetPhysicalBaseAddress() const {
-            return DecodeAddressRegister(base_address);
-        }
-
-        // Descriptor for internal vertex attributes
-        union {
-            BitField<0, 2, VertexAttributeFormat> format0; // size of one element
-            BitField<2, 2, u64> size0;                     // number of elements minus 1
-            BitField<4, 2, VertexAttributeFormat> format1;
-            BitField<6, 2, u64> size1;
-            BitField<8, 2, VertexAttributeFormat> format2;
-            BitField<10, 2, u64> size2;
-            BitField<12, 2, VertexAttributeFormat> format3;
-            BitField<14, 2, u64> size3;
-            BitField<16, 2, VertexAttributeFormat> format4;
-            BitField<18, 2, u64> size4;
-            BitField<20, 2, VertexAttributeFormat> format5;
-            BitField<22, 2, u64> size5;
-            BitField<24, 2, VertexAttributeFormat> format6;
-            BitField<26, 2, u64> size6;
-            BitField<28, 2, VertexAttributeFormat> format7;
-            BitField<30, 2, u64> size7;
-            BitField<32, 2, VertexAttributeFormat> format8;
-            BitField<34, 2, u64> size8;
-            BitField<36, 2, VertexAttributeFormat> format9;
-            BitField<38, 2, u64> size9;
-            BitField<40, 2, VertexAttributeFormat> format10;
-            BitField<42, 2, u64> size10;
-            BitField<44, 2, VertexAttributeFormat> format11;
-            BitField<46, 2, u64> size11;
-
-            BitField<48, 12, u64> attribute_mask;
-
-            // number of total attributes minus 1
-            BitField<60, 4, u64> max_attribute_index;
-        };
-
-        inline VertexAttributeFormat GetFormat(int n) const {
-            VertexAttributeFormat formats[] = {format0, format1, format2,  format3,
-                                               format4, format5, format6,  format7,
-                                               format8, format9, format10, format11};
-            return formats[n];
-        }
-
-        inline int GetNumElements(int n) const {
-            u64 sizes[] = {size0, size1, size2, size3, size4,  size5,
-                           size6, size7, size8, size9, size10, size11};
-            return (int)sizes[n] + 1;
-        }
-
-        inline int GetElementSizeInBytes(int n) const {
-            return (GetFormat(n) == VertexAttributeFormat::FLOAT)
-                       ? 4
-                       : (GetFormat(n) == VertexAttributeFormat::SHORT) ? 2 : 1;
-        }
-
-        inline int GetStride(int n) const {
-            return GetNumElements(n) * GetElementSizeInBytes(n);
-        }
-
-        inline bool IsDefaultAttribute(int id) const {
-            return (id >= 12) || (attribute_mask & (1ULL << id)) != 0;
-        }
-
-        inline int GetNumTotalAttributes() const {
-            return (int)max_attribute_index + 1;
-        }
-
-        // Attribute loaders map the source vertex data to input attributes
-        // This e.g. allows to load different attributes from different memory locations
-        struct {
-            // Source attribute data offset from the base address
-            u32 data_offset;
-
-            union {
-                BitField<0, 4, u64> comp0;
-                BitField<4, 4, u64> comp1;
-                BitField<8, 4, u64> comp2;
-                BitField<12, 4, u64> comp3;
-                BitField<16, 4, u64> comp4;
-                BitField<20, 4, u64> comp5;
-                BitField<24, 4, u64> comp6;
-                BitField<28, 4, u64> comp7;
-                BitField<32, 4, u64> comp8;
-                BitField<36, 4, u64> comp9;
-                BitField<40, 4, u64> comp10;
-                BitField<44, 4, u64> comp11;
-
-                // bytes for a single vertex in this loader
-                BitField<48, 8, u64> byte_count;
-
-                BitField<60, 4, u64> component_count;
-            };
-
-            inline int GetComponent(int n) const {
-                u64 components[] = {comp0, comp1, comp2, comp3, comp4,  comp5,
-                                    comp6, comp7, comp8, comp9, comp10, comp11};
-                return (int)components[n];
-            }
-        } attribute_loaders[12];
-    } vertex_attributes;
-
-    struct {
-        enum IndexFormat : u32 {
-            BYTE = 0,
-            SHORT = 1,
-        };
-
-        union {
-            BitField<0, 31, u32> offset; // relative to base attribute address
-            BitField<31, 1, IndexFormat> format;
-        };
-    } index_array;
-
-    // Number of vertices to render
-    u32 num_vertices;
-
-    INSERT_PADDING_WORDS(0x1);
-
-    // The index of the first vertex to render
-    u32 vertex_offset;
-
-    INSERT_PADDING_WORDS(0x3);
-
-    // These two trigger rendering of triangles
-    u32 trigger_draw;
-    u32 trigger_draw_indexed;
-
-    INSERT_PADDING_WORDS(0x2);
-
-    // These registers are used to setup the default "fall-back" vertex shader attributes
-    struct {
-        // Index of the current default attribute
-        u32 index;
-
-        // Writing to these registers sets the "current" default attribute.
-        u32 set_value[3];
-    } vs_default_attributes_setup;
-
-    INSERT_PADDING_WORDS(0x2);
-
-    struct {
-        // There are two channels that can be used to configure the next command buffer, which
-        // can be then executed by writing to the "trigger" registers. There are two reasons why a
-        // game might use this feature:
-        //  1) With this, an arbitrary number of additional command buffers may be executed in
-        //     sequence without requiring any intervention of the CPU after the initial one is
-        //     kicked off.
-        //  2) Games can configure these registers to provide a command list subroutine mechanism.
-
-        BitField<0, 20, u32> size[2]; ///< Size (in bytes / 8) of each channel's command buffer
-        BitField<0, 28, u32> addr[2]; ///< Physical address / 8 of each channel's command buffer
-        u32 trigger[2]; ///< Triggers execution of the channel's command buffer when written to
-
-        unsigned GetSize(unsigned index) const {
-            ASSERT(index < 2);
-            return 8 * size[index];
-        }
-
-        PAddr GetPhysicalAddress(unsigned index) const {
-            ASSERT(index < 2);
-            return (PAddr)(8 * addr[index]);
-        }
-    } command_buffer;
-
-    INSERT_PADDING_WORDS(4);
-
-    /// Number of input attributes to the vertex shader minus 1
-    BitField<0, 4, u32> max_input_attrib_index;
-
-    INSERT_PADDING_WORDS(2);
-
-    enum class GPUMode : u32 {
-        Drawing = 0,
-        Configuring = 1,
-    };
-
-    GPUMode gpu_mode;
-
-    INSERT_PADDING_WORDS(0x18);
-
-    enum class TriangleTopology : u32 {
-        List = 0,
-        Strip = 1,
-        Fan = 2,
-        Shader = 3, // Programmable setup unit implemented in a geometry shader
-    };
-
-    BitField<8, 2, TriangleTopology> triangle_topology;
-
-    u32 restart_primitive;
-
-    INSERT_PADDING_WORDS(0x20);
-
-    struct ShaderConfig {
-        BitField<0, 16, u32> bool_uniforms;
-
-        union {
-            BitField<0, 8, u32> x;
-            BitField<8, 8, u32> y;
-            BitField<16, 8, u32> z;
-            BitField<24, 8, u32> w;
-        } int_uniforms[4];
-
-        INSERT_PADDING_WORDS(0x4);
-
-        union {
-            // Number of input attributes to shader unit - 1
-            BitField<0, 4, u32> max_input_attribute_index;
-        };
-
-        // Offset to shader program entry point (in words)
-        BitField<0, 16, u32> main_offset;
-
-        /// Maps input attributes to registers. 4-bits per attribute, specifying a register index
-        u32 input_attribute_to_register_map_low;
-        u32 input_attribute_to_register_map_high;
-
-        unsigned int GetRegisterForAttribute(unsigned int attribute_index) const {
-            u64 map = ((u64)input_attribute_to_register_map_high << 32) |
-                      (u64)input_attribute_to_register_map_low;
-            return (map >> (attribute_index * 4)) & 0b1111;
-        }
-
-        BitField<0, 16, u32> output_mask;
-
-        // 0x28E, CODETRANSFER_END
-        INSERT_PADDING_WORDS(0x2);
-
-        struct {
-            enum Format : u32 {
-                FLOAT24 = 0,
-                FLOAT32 = 1,
-            };
-
-            bool IsFloat32() const {
-                return format == FLOAT32;
-            }
-
-            union {
-                // Index of the next uniform to write to
-                // TODO: ctrulib uses 8 bits for this, however that seems to yield lots of invalid
-                // indices
-                // TODO: Maybe the uppermost index is for the geometry shader? Investigate!
-                BitField<0, 7, u32> index;
-
-                BitField<31, 1, Format> format;
-            };
-
-            // Writing to these registers sets the current uniform.
-            u32 set_value[8];
-
-        } uniform_setup;
-
-        INSERT_PADDING_WORDS(0x2);
-
-        struct {
-            // Offset of the next instruction to write code to.
-            // Incremented with each instruction write.
-            u32 offset;
-
-            // Writing to these registers sets the "current" word in the shader program.
-            u32 set_word[8];
-        } program;
-
-        INSERT_PADDING_WORDS(0x1);
-
-        // This register group is used to load an internal table of swizzling patterns,
-        // which are indexed by each shader instruction to specify vector component swizzling.
-        struct {
-            // Offset of the next swizzle pattern to write code to.
-            // Incremented with each instruction write.
-            u32 offset;
-
-            // Writing to these registers sets the current swizzle pattern in the table.
-            u32 set_word[8];
-        } swizzle_patterns;
-
-        INSERT_PADDING_WORDS(0x2);
-    };
-
-    ShaderConfig gs;
-    ShaderConfig vs;
-
-    INSERT_PADDING_WORDS(0x20);
-
-    // Map register indices to names readable by humans
-    // Used for debugging purposes, so performance is not an issue here
-    static std::string GetCommandName(int index);
-
-    static constexpr size_t NumIds() {
-        return sizeof(Regs) / sizeof(u32);
-    }
-
-    const u32& operator[](int index) const {
-        const u32* content = reinterpret_cast<const u32*>(this);
-        return content[index];
-    }
-
-    u32& operator[](int index) {
-        u32* content = reinterpret_cast<u32*>(this);
-        return content[index];
-    }
-
-private:
-    /*
-     * Most physical addresses which Pica registers refer to are 8-byte aligned.
-     * This function should be used to get the address from a raw register value.
-     */
-    static inline u32 DecodeAddressRegister(u32 register_value) {
-        return register_value * 8;
-    }
-};
-
-// TODO: MSVC does not support using offsetof() on non-static data members even though this
-//       is technically allowed since C++11. This macro should be enabled once MSVC adds
-//       support for that.
-#ifndef _MSC_VER
-#define ASSERT_REG_POSITION(field_name, position)                                                  \
-    static_assert(offsetof(Regs, field_name) == position * 4,                                      \
-                  "Field " #field_name " has invalid position")
-
-ASSERT_REG_POSITION(trigger_irq, 0x10);
-ASSERT_REG_POSITION(cull_mode, 0x40);
-ASSERT_REG_POSITION(viewport_size_x, 0x41);
-ASSERT_REG_POSITION(viewport_size_y, 0x43);
-ASSERT_REG_POSITION(viewport_depth_range, 0x4d);
-ASSERT_REG_POSITION(viewport_depth_near_plane, 0x4e);
-ASSERT_REG_POSITION(vs_output_attributes[0], 0x50);
-ASSERT_REG_POSITION(vs_output_attributes[1], 0x51);
-ASSERT_REG_POSITION(scissor_test, 0x65);
-ASSERT_REG_POSITION(viewport_corner, 0x68);
-ASSERT_REG_POSITION(depthmap_enable, 0x6D);
-ASSERT_REG_POSITION(texture0_enable, 0x80);
-ASSERT_REG_POSITION(texture0, 0x81);
-ASSERT_REG_POSITION(texture0_format, 0x8e);
-ASSERT_REG_POSITION(fragment_lighting_enable, 0x8f);
-ASSERT_REG_POSITION(texture1, 0x91);
-ASSERT_REG_POSITION(texture1_format, 0x96);
-ASSERT_REG_POSITION(texture2, 0x99);
-ASSERT_REG_POSITION(texture2_format, 0x9e);
-ASSERT_REG_POSITION(tev_stage0, 0xc0);
-ASSERT_REG_POSITION(tev_stage1, 0xc8);
-ASSERT_REG_POSITION(tev_stage2, 0xd0);
-ASSERT_REG_POSITION(tev_stage3, 0xd8);
-ASSERT_REG_POSITION(tev_combiner_buffer_input, 0xe0);
-ASSERT_REG_POSITION(fog_mode, 0xe0);
-ASSERT_REG_POSITION(fog_color, 0xe1);
-ASSERT_REG_POSITION(fog_lut_offset, 0xe6);
-ASSERT_REG_POSITION(fog_lut_data, 0xe8);
-ASSERT_REG_POSITION(tev_stage4, 0xf0);
-ASSERT_REG_POSITION(tev_stage5, 0xf8);
-ASSERT_REG_POSITION(tev_combiner_buffer_color, 0xfd);
-ASSERT_REG_POSITION(output_merger, 0x100);
-ASSERT_REG_POSITION(framebuffer, 0x110);
-ASSERT_REG_POSITION(lighting, 0x140);
-ASSERT_REG_POSITION(vertex_attributes, 0x200);
-ASSERT_REG_POSITION(index_array, 0x227);
-ASSERT_REG_POSITION(num_vertices, 0x228);
-ASSERT_REG_POSITION(vertex_offset, 0x22a);
-ASSERT_REG_POSITION(trigger_draw, 0x22e);
-ASSERT_REG_POSITION(trigger_draw_indexed, 0x22f);
-ASSERT_REG_POSITION(vs_default_attributes_setup, 0x232);
-ASSERT_REG_POSITION(command_buffer, 0x238);
-ASSERT_REG_POSITION(gpu_mode, 0x245);
-ASSERT_REG_POSITION(triangle_topology, 0x25e);
-ASSERT_REG_POSITION(restart_primitive, 0x25f);
-ASSERT_REG_POSITION(gs, 0x280);
-ASSERT_REG_POSITION(vs, 0x2b0);
-
-#undef ASSERT_REG_POSITION
-#endif // !defined(_MSC_VER)
-
-static_assert(sizeof(Regs::ShaderConfig) == 0x30 * sizeof(u32),
-              "ShaderConfig structure has incorrect size");
-
-// The total number of registers is chosen arbitrarily, but let's make sure it's not some odd value
-// anyway.
-static_assert(sizeof(Regs) <= 0x300 * sizeof(u32),
-              "Register set structure larger than it should be");
-static_assert(sizeof(Regs) >= 0x300 * sizeof(u32),
-              "Register set structure smaller than it should be");
-
 /// Initialize Pica state
 void Init();
 
diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h
index 785d056504..af7536d11e 100644
--- a/src/video_core/pica_state.h
+++ b/src/video_core/pica_state.h
@@ -7,8 +7,8 @@
 #include <array>
 #include "common/bit_field.h"
 #include "common/common_types.h"
-#include "video_core/pica.h"
 #include "video_core/primitive_assembly.h"
+#include "video_core/regs.h"
 #include "video_core/shader/shader.h"
 
 namespace Pica {
diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp
index e71ff57198..acd2ac5e23 100644
--- a/src/video_core/primitive_assembly.cpp
+++ b/src/video_core/primitive_assembly.cpp
@@ -3,14 +3,14 @@
 // Refer to the license.txt file included.
 
 #include "common/logging/log.h"
-#include "video_core/pica.h"
 #include "video_core/primitive_assembly.h"
+#include "video_core/regs_pipeline.h"
 #include "video_core/shader/shader.h"
 
 namespace Pica {
 
 template <typename VertexType>
-PrimitiveAssembler<VertexType>::PrimitiveAssembler(Regs::TriangleTopology topology)
+PrimitiveAssembler<VertexType>::PrimitiveAssembler(PipelineRegs::TriangleTopology topology)
     : topology(topology), buffer_index(0) {}
 
 template <typename VertexType>
@@ -18,8 +18,8 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
                                                   TriangleHandler triangle_handler) {
     switch (topology) {
     // TODO: Figure out what's different with TriangleTopology::Shader.
-    case Regs::TriangleTopology::List:
-    case Regs::TriangleTopology::Shader:
+    case PipelineRegs::TriangleTopology::List:
+    case PipelineRegs::TriangleTopology::Shader:
         if (buffer_index < 2) {
             buffer[buffer_index++] = vtx;
         } else {
@@ -29,8 +29,8 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
         }
         break;
 
-    case Regs::TriangleTopology::Strip:
-    case Regs::TriangleTopology::Fan:
+    case PipelineRegs::TriangleTopology::Strip:
+    case PipelineRegs::TriangleTopology::Fan:
         if (strip_ready)
             triangle_handler(buffer[0], buffer[1], vtx);
 
@@ -38,9 +38,9 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
 
         strip_ready |= (buffer_index == 1);
 
-        if (topology == Regs::TriangleTopology::Strip)
+        if (topology == PipelineRegs::TriangleTopology::Strip)
             buffer_index = !buffer_index;
-        else if (topology == Regs::TriangleTopology::Fan)
+        else if (topology == PipelineRegs::TriangleTopology::Fan)
             buffer_index = 1;
         break;
 
@@ -57,7 +57,7 @@ void PrimitiveAssembler<VertexType>::Reset() {
 }
 
 template <typename VertexType>
-void PrimitiveAssembler<VertexType>::Reconfigure(Regs::TriangleTopology topology) {
+void PrimitiveAssembler<VertexType>::Reconfigure(PipelineRegs::TriangleTopology topology) {
     Reset();
     this->topology = topology;
 }
diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h
index 24da47382b..e8eccdf276 100644
--- a/src/video_core/primitive_assembly.h
+++ b/src/video_core/primitive_assembly.h
@@ -5,7 +5,7 @@
 #pragma once
 
 #include <functional>
-#include "video_core/pica.h"
+#include "video_core/regs_pipeline.h"
 
 namespace Pica {
 
@@ -18,7 +18,8 @@ struct PrimitiveAssembler {
     using TriangleHandler =
         std::function<void(const VertexType& v0, const VertexType& v1, const VertexType& v2)>;
 
-    PrimitiveAssembler(Regs::TriangleTopology topology = Regs::TriangleTopology::List);
+    PrimitiveAssembler(
+        PipelineRegs::TriangleTopology topology = PipelineRegs::TriangleTopology::List);
 
     /*
      * Queues a vertex, builds primitives from the vertex queue according to the given
@@ -36,10 +37,10 @@ struct PrimitiveAssembler {
     /**
      * Reconfigures the PrimitiveAssembler to use a different triangle topology.
      */
-    void Reconfigure(Regs::TriangleTopology topology);
+    void Reconfigure(PipelineRegs::TriangleTopology topology);
 
 private:
-    Regs::TriangleTopology topology;
+    PipelineRegs::TriangleTopology topology;
 
     int buffer_index;
     VertexType buffer[2];
diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp
index 287d732b55..ca09c9d0eb 100644
--- a/src/video_core/rasterizer.cpp
+++ b/src/video_core/rasterizer.cpp
@@ -16,10 +16,10 @@
 #include "core/hw/gpu.h"
 #include "core/memory.h"
 #include "video_core/debug_utils/debug_utils.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
 #include "video_core/pica_types.h"
 #include "video_core/rasterizer.h"
+#include "video_core/regs.h"
 #include "video_core/shader/shader.h"
 #include "video_core/texture/texture_decode.h"
 #include "video_core/utils.h"
@@ -29,7 +29,7 @@ namespace Pica {
 namespace Rasterizer {
 
 static void DrawPixel(int x, int y, const Math::Vec4<u8>& color) {
-    const auto& framebuffer = g_state.regs.framebuffer;
+    const auto& framebuffer = g_state.regs.framebuffer.framebuffer;
     const PAddr addr = framebuffer.GetColorBufferPhysicalAddress();
 
     // Similarly to textures, the render framebuffer is laid out from bottom to top, too.
@@ -44,23 +44,23 @@ static void DrawPixel(int x, int y, const Math::Vec4<u8>& color) {
     u8* dst_pixel = Memory::GetPhysicalPointer(addr) + dst_offset;
 
     switch (framebuffer.color_format) {
-    case Regs::ColorFormat::RGBA8:
+    case FramebufferRegs::ColorFormat::RGBA8:
         Color::EncodeRGBA8(color, dst_pixel);
         break;
 
-    case Regs::ColorFormat::RGB8:
+    case FramebufferRegs::ColorFormat::RGB8:
         Color::EncodeRGB8(color, dst_pixel);
         break;
 
-    case Regs::ColorFormat::RGB5A1:
+    case FramebufferRegs::ColorFormat::RGB5A1:
         Color::EncodeRGB5A1(color, dst_pixel);
         break;
 
-    case Regs::ColorFormat::RGB565:
+    case FramebufferRegs::ColorFormat::RGB565:
         Color::EncodeRGB565(color, dst_pixel);
         break;
 
-    case Regs::ColorFormat::RGBA4:
+    case FramebufferRegs::ColorFormat::RGBA4:
         Color::EncodeRGBA4(color, dst_pixel);
         break;
 
@@ -72,7 +72,7 @@ static void DrawPixel(int x, int y, const Math::Vec4<u8>& color) {
 }
 
 static const Math::Vec4<u8> GetPixel(int x, int y) {
-    const auto& framebuffer = g_state.regs.framebuffer;
+    const auto& framebuffer = g_state.regs.framebuffer.framebuffer;
     const PAddr addr = framebuffer.GetColorBufferPhysicalAddress();
 
     y = framebuffer.height - y;
@@ -85,19 +85,19 @@ static const Math::Vec4<u8> GetPixel(int x, int y) {
     u8* src_pixel = Memory::GetPhysicalPointer(addr) + src_offset;
 
     switch (framebuffer.color_format) {
-    case Regs::ColorFormat::RGBA8:
+    case FramebufferRegs::ColorFormat::RGBA8:
         return Color::DecodeRGBA8(src_pixel);
 
-    case Regs::ColorFormat::RGB8:
+    case FramebufferRegs::ColorFormat::RGB8:
         return Color::DecodeRGB8(src_pixel);
 
-    case Regs::ColorFormat::RGB5A1:
+    case FramebufferRegs::ColorFormat::RGB5A1:
         return Color::DecodeRGB5A1(src_pixel);
 
-    case Regs::ColorFormat::RGB565:
+    case FramebufferRegs::ColorFormat::RGB565:
         return Color::DecodeRGB565(src_pixel);
 
-    case Regs::ColorFormat::RGBA4:
+    case FramebufferRegs::ColorFormat::RGBA4:
         return Color::DecodeRGBA4(src_pixel);
 
     default:
@@ -110,25 +110,25 @@ static const Math::Vec4<u8> GetPixel(int x, int y) {
 }
 
 static u32 GetDepth(int x, int y) {
-    const auto& framebuffer = g_state.regs.framebuffer;
+    const auto& framebuffer = g_state.regs.framebuffer.framebuffer;
     const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress();
     u8* depth_buffer = Memory::GetPhysicalPointer(addr);
 
     y = framebuffer.height - y;
 
     const u32 coarse_y = y & ~7;
-    u32 bytes_per_pixel = Regs::BytesPerDepthPixel(framebuffer.depth_format);
+    u32 bytes_per_pixel = FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format);
     u32 stride = framebuffer.width * bytes_per_pixel;
 
     u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
     u8* src_pixel = depth_buffer + src_offset;
 
     switch (framebuffer.depth_format) {
-    case Regs::DepthFormat::D16:
+    case FramebufferRegs::DepthFormat::D16:
         return Color::DecodeD16(src_pixel);
-    case Regs::DepthFormat::D24:
+    case FramebufferRegs::DepthFormat::D24:
         return Color::DecodeD24(src_pixel);
-    case Regs::DepthFormat::D24S8:
+    case FramebufferRegs::DepthFormat::D24S8:
         return Color::DecodeD24S8(src_pixel).x;
     default:
         LOG_CRITICAL(HW_GPU, "Unimplemented depth format %u", framebuffer.depth_format);
@@ -138,21 +138,21 @@ static u32 GetDepth(int x, int y) {
 }
 
 static u8 GetStencil(int x, int y) {
-    const auto& framebuffer = g_state.regs.framebuffer;
+    const auto& framebuffer = g_state.regs.framebuffer.framebuffer;
     const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress();
     u8* depth_buffer = Memory::GetPhysicalPointer(addr);
 
     y = framebuffer.height - y;
 
     const u32 coarse_y = y & ~7;
-    u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(framebuffer.depth_format);
+    u32 bytes_per_pixel = Pica::FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format);
     u32 stride = framebuffer.width * bytes_per_pixel;
 
     u32 src_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
     u8* src_pixel = depth_buffer + src_offset;
 
     switch (framebuffer.depth_format) {
-    case Regs::DepthFormat::D24S8:
+    case FramebufferRegs::DepthFormat::D24S8:
         return Color::DecodeD24S8(src_pixel).y;
 
     default:
@@ -165,29 +165,29 @@ static u8 GetStencil(int x, int y) {
 }
 
 static void SetDepth(int x, int y, u32 value) {
-    const auto& framebuffer = g_state.regs.framebuffer;
+    const auto& framebuffer = g_state.regs.framebuffer.framebuffer;
     const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress();
     u8* depth_buffer = Memory::GetPhysicalPointer(addr);
 
     y = framebuffer.height - y;
 
     const u32 coarse_y = y & ~7;
-    u32 bytes_per_pixel = Regs::BytesPerDepthPixel(framebuffer.depth_format);
+    u32 bytes_per_pixel = FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format);
     u32 stride = framebuffer.width * bytes_per_pixel;
 
     u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
     u8* dst_pixel = depth_buffer + dst_offset;
 
     switch (framebuffer.depth_format) {
-    case Regs::DepthFormat::D16:
+    case FramebufferRegs::DepthFormat::D16:
         Color::EncodeD16(value, dst_pixel);
         break;
 
-    case Regs::DepthFormat::D24:
+    case FramebufferRegs::DepthFormat::D24:
         Color::EncodeD24(value, dst_pixel);
         break;
 
-    case Regs::DepthFormat::D24S8:
+    case FramebufferRegs::DepthFormat::D24S8:
         Color::EncodeD24X8(value, dst_pixel);
         break;
 
@@ -199,26 +199,26 @@ static void SetDepth(int x, int y, u32 value) {
 }
 
 static void SetStencil(int x, int y, u8 value) {
-    const auto& framebuffer = g_state.regs.framebuffer;
+    const auto& framebuffer = g_state.regs.framebuffer.framebuffer;
     const PAddr addr = framebuffer.GetDepthBufferPhysicalAddress();
     u8* depth_buffer = Memory::GetPhysicalPointer(addr);
 
     y = framebuffer.height - y;
 
     const u32 coarse_y = y & ~7;
-    u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(framebuffer.depth_format);
+    u32 bytes_per_pixel = Pica::FramebufferRegs::BytesPerDepthPixel(framebuffer.depth_format);
     u32 stride = framebuffer.width * bytes_per_pixel;
 
     u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
     u8* dst_pixel = depth_buffer + dst_offset;
 
     switch (framebuffer.depth_format) {
-    case Pica::Regs::DepthFormat::D16:
-    case Pica::Regs::DepthFormat::D24:
+    case Pica::FramebufferRegs::DepthFormat::D16:
+    case Pica::FramebufferRegs::DepthFormat::D24:
         // Nothing to do
         break;
 
-    case Pica::Regs::DepthFormat::D24S8:
+    case Pica::FramebufferRegs::DepthFormat::D24S8:
         Color::EncodeX24S8(value, dst_pixel);
         break;
 
@@ -229,32 +229,32 @@ static void SetStencil(int x, int y, u8 value) {
     }
 }
 
-static u8 PerformStencilAction(Regs::StencilAction action, u8 old_stencil, u8 ref) {
+static u8 PerformStencilAction(FramebufferRegs::StencilAction action, u8 old_stencil, u8 ref) {
     switch (action) {
-    case Regs::StencilAction::Keep:
+    case FramebufferRegs::StencilAction::Keep:
         return old_stencil;
 
-    case Regs::StencilAction::Zero:
+    case FramebufferRegs::StencilAction::Zero:
         return 0;
 
-    case Regs::StencilAction::Replace:
+    case FramebufferRegs::StencilAction::Replace:
         return ref;
 
-    case Regs::StencilAction::Increment:
+    case FramebufferRegs::StencilAction::Increment:
         // Saturated increment
         return std::min<u8>(old_stencil, 254) + 1;
 
-    case Regs::StencilAction::Decrement:
+    case FramebufferRegs::StencilAction::Decrement:
         // Saturated decrement
         return std::max<u8>(old_stencil, 1) - 1;
 
-    case Regs::StencilAction::Invert:
+    case FramebufferRegs::StencilAction::Invert:
         return ~old_stencil;
 
-    case Regs::StencilAction::IncrementWrap:
+    case FramebufferRegs::StencilAction::IncrementWrap:
         return old_stencil + 1;
 
-    case Regs::StencilAction::DecrementWrap:
+    case FramebufferRegs::StencilAction::DecrementWrap:
         return old_stencil - 1;
 
     default:
@@ -327,14 +327,14 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
                                   ScreenToRasterizerCoordinates(v1.screenpos),
                                   ScreenToRasterizerCoordinates(v2.screenpos)};
 
-    if (regs.cull_mode == Regs::CullMode::KeepAll) {
+    if (regs.rasterizer.cull_mode == RasterizerRegs::CullMode::KeepAll) {
         // Make sure we always end up with a triangle wound counter-clockwise
         if (!reversed && SignedArea(vtxpos[0].xy(), vtxpos[1].xy(), vtxpos[2].xy()) <= 0) {
             ProcessTriangleInternal(v0, v2, v1, true);
             return;
         }
     } else {
-        if (!reversed && regs.cull_mode == Regs::CullMode::KeepClockWise) {
+        if (!reversed && regs.rasterizer.cull_mode == RasterizerRegs::CullMode::KeepClockWise) {
             // Reverse vertex order and use the CCW code path.
             ProcessTriangleInternal(v0, v2, v1, true);
             return;
@@ -351,13 +351,13 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
     u16 max_y = std::max({vtxpos[0].y, vtxpos[1].y, vtxpos[2].y});
 
     // Convert the scissor box coordinates to 12.4 fixed point
-    u16 scissor_x1 = (u16)(regs.scissor_test.x1 << 4);
-    u16 scissor_y1 = (u16)(regs.scissor_test.y1 << 4);
+    u16 scissor_x1 = (u16)(regs.rasterizer.scissor_test.x1 << 4);
+    u16 scissor_y1 = (u16)(regs.rasterizer.scissor_test.y1 << 4);
     // x2,y2 have +1 added to cover the entire sub-pixel area
-    u16 scissor_x2 = (u16)((regs.scissor_test.x2 + 1) << 4);
-    u16 scissor_y2 = (u16)((regs.scissor_test.y2 + 1) << 4);
+    u16 scissor_x2 = (u16)((regs.rasterizer.scissor_test.x2 + 1) << 4);
+    u16 scissor_y2 = (u16)((regs.rasterizer.scissor_test.y2 + 1) << 4);
 
-    if (regs.scissor_test.mode == Regs::ScissorMode::Include) {
+    if (regs.rasterizer.scissor_test.mode == RasterizerRegs::ScissorMode::Include) {
         // Calculate the new bounds
         min_x = std::max(min_x, scissor_x1);
         min_y = std::max(min_y, scissor_y1);
@@ -397,12 +397,13 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
 
     auto w_inverse = Math::MakeVec(v0.pos.w, v1.pos.w, v2.pos.w);
 
-    auto textures = regs.GetTextures();
-    auto tev_stages = regs.GetTevStages();
+    auto textures = regs.texturing.GetTextures();
+    auto tev_stages = regs.texturing.GetTevStages();
 
-    bool stencil_action_enable = g_state.regs.output_merger.stencil_test.enable &&
-                                 g_state.regs.framebuffer.depth_format == Regs::DepthFormat::D24S8;
-    const auto stencil_test = g_state.regs.output_merger.stencil_test;
+    bool stencil_action_enable =
+        g_state.regs.framebuffer.output_merger.stencil_test.enable &&
+        g_state.regs.framebuffer.framebuffer.depth_format == FramebufferRegs::DepthFormat::D24S8;
+    const auto stencil_test = g_state.regs.framebuffer.output_merger.stencil_test;
 
     // Enter rasterization loop, starting at the center of the topleft bounding box corner.
     // TODO: Not sure if looping through x first might be faster
@@ -411,7 +412,7 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
 
             // Do not process the pixel if it's inside the scissor box and the scissor mode is set
             // to Exclude
-            if (regs.scissor_test.mode == Regs::ScissorMode::Exclude) {
+            if (regs.rasterizer.scissor_test.mode == RasterizerRegs::ScissorMode::Exclude) {
                 if (x >= scissor_x1 && x < scissor_x2 && y >= scissor_y1 && y < scissor_y2)
                     continue;
             }
@@ -441,12 +442,14 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
 
             // Not fully accurate. About 3 bits in precision are missing.
             // Z-Buffer (z / w * scale + offset)
-            float depth_scale = float24::FromRaw(regs.viewport_depth_range).ToFloat32();
-            float depth_offset = float24::FromRaw(regs.viewport_depth_near_plane).ToFloat32();
+            float depth_scale = float24::FromRaw(regs.rasterizer.viewport_depth_range).ToFloat32();
+            float depth_offset =
+                float24::FromRaw(regs.rasterizer.viewport_depth_near_plane).ToFloat32();
             float depth = interpolated_z_over_w * depth_scale + depth_offset;
 
             // Potentially switch to W-Buffer
-            if (regs.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) {
+            if (regs.rasterizer.depthmap_enable ==
+                Pica::RasterizerRegs::DepthBuffering::WBuffering) {
                 // W-Buffer (z * scale + w * offset = (z / w * scale + offset) * w)
                 depth *= interpolated_w_inverse.ToFloat32() * wsum;
             }
@@ -513,9 +516,9 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
                 // TODO: Refactor so cubemaps and shadowmaps can be handled
                 if (i == 0) {
                     switch (texture.config.type) {
-                    case Regs::TextureConfig::Texture2D:
+                    case TexturingRegs::TextureConfig::Texture2D:
                         break;
-                    case Regs::TextureConfig::Projection2D: {
+                    case TexturingRegs::TextureConfig::Projection2D: {
                         auto tc0_w = GetInterpolatedAttribute(v0.tc0_w, v1.tc0_w, v2.tc0_w);
                         u /= tc0_w;
                         v /= tc0_w;
@@ -534,21 +537,21 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
                 int t = (int)(v * float24::FromFloat32(static_cast<float>(texture.config.height)))
                             .ToFloat32();
 
-                static auto GetWrappedTexCoord = [](Regs::TextureConfig::WrapMode mode, int val,
-                                                    unsigned size) {
+                static auto GetWrappedTexCoord = [](TexturingRegs::TextureConfig::WrapMode mode,
+                                                    int val, unsigned size) {
                     switch (mode) {
-                    case Regs::TextureConfig::ClampToEdge:
+                    case TexturingRegs::TextureConfig::ClampToEdge:
                         val = std::max(val, 0);
                         val = std::min(val, (int)size - 1);
                         return val;
 
-                    case Regs::TextureConfig::ClampToBorder:
+                    case TexturingRegs::TextureConfig::ClampToBorder:
                         return val;
 
-                    case Regs::TextureConfig::Repeat:
+                    case TexturingRegs::TextureConfig::Repeat:
                         return (int)((unsigned)val % size);
 
-                    case Regs::TextureConfig::MirroredRepeat: {
+                    case TexturingRegs::TextureConfig::MirroredRepeat: {
                         unsigned int coord = ((unsigned)val % (2 * size));
                         if (coord >= size)
                             coord = 2 * size - 1 - coord;
@@ -562,9 +565,9 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
                     }
                 };
 
-                if ((texture.config.wrap_s == Regs::TextureConfig::ClampToBorder &&
+                if ((texture.config.wrap_s == TexturingRegs::TextureConfig::ClampToBorder &&
                      (s < 0 || static_cast<u32>(s) >= texture.config.width)) ||
-                    (texture.config.wrap_t == Regs::TextureConfig::ClampToBorder &&
+                    (texture.config.wrap_t == TexturingRegs::TextureConfig::ClampToBorder &&
                      (t < 0 || static_cast<u32>(t) >= texture.config.height))) {
                     auto border_color = texture.config.border_color;
                     texture_color[i] = {border_color.r, border_color.g, border_color.b,
@@ -600,17 +603,19 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
             Math::Vec4<u8> combiner_output;
             Math::Vec4<u8> combiner_buffer = {0, 0, 0, 0};
             Math::Vec4<u8> next_combiner_buffer = {
-                regs.tev_combiner_buffer_color.r, regs.tev_combiner_buffer_color.g,
-                regs.tev_combiner_buffer_color.b, regs.tev_combiner_buffer_color.a,
+                regs.texturing.tev_combiner_buffer_color.r,
+                regs.texturing.tev_combiner_buffer_color.g,
+                regs.texturing.tev_combiner_buffer_color.b,
+                regs.texturing.tev_combiner_buffer_color.a,
             };
 
             for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size();
                  ++tev_stage_index) {
                 const auto& tev_stage = tev_stages[tev_stage_index];
-                using Source = Regs::TevStageConfig::Source;
-                using ColorModifier = Regs::TevStageConfig::ColorModifier;
-                using AlphaModifier = Regs::TevStageConfig::AlphaModifier;
-                using Operation = Regs::TevStageConfig::Operation;
+                using Source = TexturingRegs::TevStageConfig::Source;
+                using ColorModifier = TexturingRegs::TevStageConfig::ColorModifier;
+                using AlphaModifier = TexturingRegs::TevStageConfig::AlphaModifier;
+                using Operation = TexturingRegs::TevStageConfig::Operation;
 
                 auto GetSource = [&](Source source) -> Math::Vec4<u8> {
                     switch (source) {
@@ -862,54 +867,54 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
 
                 combiner_buffer = next_combiner_buffer;
 
-                if (regs.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferColor(
+                if (regs.texturing.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferColor(
                         tev_stage_index)) {
                     next_combiner_buffer.r() = combiner_output.r();
                     next_combiner_buffer.g() = combiner_output.g();
                     next_combiner_buffer.b() = combiner_output.b();
                 }
 
-                if (regs.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferAlpha(
+                if (regs.texturing.tev_combiner_buffer_input.TevStageUpdatesCombinerBufferAlpha(
                         tev_stage_index)) {
                     next_combiner_buffer.a() = combiner_output.a();
                 }
             }
 
-            const auto& output_merger = regs.output_merger;
+            const auto& output_merger = regs.framebuffer.output_merger;
             // TODO: Does alpha testing happen before or after stencil?
             if (output_merger.alpha_test.enable) {
                 bool pass = false;
 
                 switch (output_merger.alpha_test.func) {
-                case Regs::CompareFunc::Never:
+                case FramebufferRegs::CompareFunc::Never:
                     pass = false;
                     break;
 
-                case Regs::CompareFunc::Always:
+                case FramebufferRegs::CompareFunc::Always:
                     pass = true;
                     break;
 
-                case Regs::CompareFunc::Equal:
+                case FramebufferRegs::CompareFunc::Equal:
                     pass = combiner_output.a() == output_merger.alpha_test.ref;
                     break;
 
-                case Regs::CompareFunc::NotEqual:
+                case FramebufferRegs::CompareFunc::NotEqual:
                     pass = combiner_output.a() != output_merger.alpha_test.ref;
                     break;
 
-                case Regs::CompareFunc::LessThan:
+                case FramebufferRegs::CompareFunc::LessThan:
                     pass = combiner_output.a() < output_merger.alpha_test.ref;
                     break;
 
-                case Regs::CompareFunc::LessThanOrEqual:
+                case FramebufferRegs::CompareFunc::LessThanOrEqual:
                     pass = combiner_output.a() <= output_merger.alpha_test.ref;
                     break;
 
-                case Regs::CompareFunc::GreaterThan:
+                case FramebufferRegs::CompareFunc::GreaterThan:
                     pass = combiner_output.a() > output_merger.alpha_test.ref;
                     break;
 
-                case Regs::CompareFunc::GreaterThanOrEqual:
+                case FramebufferRegs::CompareFunc::GreaterThanOrEqual:
                     pass = combiner_output.a() >= output_merger.alpha_test.ref;
                     break;
                 }
@@ -922,16 +927,16 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
             // Not fully accurate. We'd have to know what data type is used to
             // store the depth etc. Using float for now until we know more
             // about Pica datatypes
-            if (regs.fog_mode == Regs::FogMode::Fog) {
+            if (regs.texturing.fog_mode == TexturingRegs::FogMode::Fog) {
                 const Math::Vec3<u8> fog_color = {
-                    static_cast<u8>(regs.fog_color.r.Value()),
-                    static_cast<u8>(regs.fog_color.g.Value()),
-                    static_cast<u8>(regs.fog_color.b.Value()),
+                    static_cast<u8>(regs.texturing.fog_color.r.Value()),
+                    static_cast<u8>(regs.texturing.fog_color.g.Value()),
+                    static_cast<u8>(regs.texturing.fog_color.b.Value()),
                 };
 
                 // Get index into fog LUT
                 float fog_index;
-                if (g_state.regs.fog_flip) {
+                if (g_state.regs.texturing.fog_flip) {
                     fog_index = (1.0f - depth) * 128.0f;
                 } else {
                     fog_index = depth * 128.0f;
@@ -955,10 +960,10 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
             u8 old_stencil = 0;
 
             auto UpdateStencil = [stencil_test, x, y,
-                                  &old_stencil](Pica::Regs::StencilAction action) {
+                                  &old_stencil](Pica::FramebufferRegs::StencilAction action) {
                 u8 new_stencil =
                     PerformStencilAction(action, old_stencil, stencil_test.reference_value);
-                if (g_state.regs.framebuffer.allow_depth_stencil_write != 0)
+                if (g_state.regs.framebuffer.framebuffer.allow_depth_stencil_write != 0)
                     SetStencil(x >> 4, y >> 4, (new_stencil & stencil_test.write_mask) |
                                                    (old_stencil & ~stencil_test.write_mask));
             };
@@ -970,35 +975,35 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
 
                 bool pass = false;
                 switch (stencil_test.func) {
-                case Regs::CompareFunc::Never:
+                case FramebufferRegs::CompareFunc::Never:
                     pass = false;
                     break;
 
-                case Regs::CompareFunc::Always:
+                case FramebufferRegs::CompareFunc::Always:
                     pass = true;
                     break;
 
-                case Regs::CompareFunc::Equal:
+                case FramebufferRegs::CompareFunc::Equal:
                     pass = (ref == dest);
                     break;
 
-                case Regs::CompareFunc::NotEqual:
+                case FramebufferRegs::CompareFunc::NotEqual:
                     pass = (ref != dest);
                     break;
 
-                case Regs::CompareFunc::LessThan:
+                case FramebufferRegs::CompareFunc::LessThan:
                     pass = (ref < dest);
                     break;
 
-                case Regs::CompareFunc::LessThanOrEqual:
+                case FramebufferRegs::CompareFunc::LessThanOrEqual:
                     pass = (ref <= dest);
                     break;
 
-                case Regs::CompareFunc::GreaterThan:
+                case FramebufferRegs::CompareFunc::GreaterThan:
                     pass = (ref > dest);
                     break;
 
-                case Regs::CompareFunc::GreaterThanOrEqual:
+                case FramebufferRegs::CompareFunc::GreaterThanOrEqual:
                     pass = (ref >= dest);
                     break;
                 }
@@ -1010,7 +1015,8 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
             }
 
             // Convert float to integer
-            unsigned num_bits = Regs::DepthBitsPerPixel(regs.framebuffer.depth_format);
+            unsigned num_bits =
+                FramebufferRegs::DepthBitsPerPixel(regs.framebuffer.framebuffer.depth_format);
             u32 z = (u32)(depth * ((1 << num_bits) - 1));
 
             if (output_merger.depth_test_enable) {
@@ -1019,35 +1025,35 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
                 bool pass = false;
 
                 switch (output_merger.depth_test_func) {
-                case Regs::CompareFunc::Never:
+                case FramebufferRegs::CompareFunc::Never:
                     pass = false;
                     break;
 
-                case Regs::CompareFunc::Always:
+                case FramebufferRegs::CompareFunc::Always:
                     pass = true;
                     break;
 
-                case Regs::CompareFunc::Equal:
+                case FramebufferRegs::CompareFunc::Equal:
                     pass = z == ref_z;
                     break;
 
-                case Regs::CompareFunc::NotEqual:
+                case FramebufferRegs::CompareFunc::NotEqual:
                     pass = z != ref_z;
                     break;
 
-                case Regs::CompareFunc::LessThan:
+                case FramebufferRegs::CompareFunc::LessThan:
                     pass = z < ref_z;
                     break;
 
-                case Regs::CompareFunc::LessThanOrEqual:
+                case FramebufferRegs::CompareFunc::LessThanOrEqual:
                     pass = z <= ref_z;
                     break;
 
-                case Regs::CompareFunc::GreaterThan:
+                case FramebufferRegs::CompareFunc::GreaterThan:
                     pass = z > ref_z;
                     break;
 
-                case Regs::CompareFunc::GreaterThanOrEqual:
+                case FramebufferRegs::CompareFunc::GreaterThanOrEqual:
                     pass = z >= ref_z;
                     break;
                 }
@@ -1059,8 +1065,11 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
                 }
             }
 
-            if (regs.framebuffer.allow_depth_stencil_write != 0 && output_merger.depth_write_enable)
+            if (regs.framebuffer.framebuffer.allow_depth_stencil_write != 0 &&
+                output_merger.depth_write_enable) {
+
                 SetDepth(x >> 4, y >> 4, z);
+            }
 
             // The stencil depth_pass action is executed even if depth testing is disabled
             if (stencil_action_enable)
@@ -1072,7 +1081,8 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
             if (output_merger.alphablend_enable) {
                 auto params = output_merger.alpha_blending;
 
-                auto LookupFactor = [&](unsigned channel, Regs::BlendFactor factor) -> u8 {
+                auto LookupFactor = [&](unsigned channel,
+                                        FramebufferRegs::BlendFactor factor) -> u8 {
                     DEBUG_ASSERT(channel < 4);
 
                     const Math::Vec4<u8> blend_const = {
@@ -1083,49 +1093,49 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
                     };
 
                     switch (factor) {
-                    case Regs::BlendFactor::Zero:
+                    case FramebufferRegs::BlendFactor::Zero:
                         return 0;
 
-                    case Regs::BlendFactor::One:
+                    case FramebufferRegs::BlendFactor::One:
                         return 255;
 
-                    case Regs::BlendFactor::SourceColor:
+                    case FramebufferRegs::BlendFactor::SourceColor:
                         return combiner_output[channel];
 
-                    case Regs::BlendFactor::OneMinusSourceColor:
+                    case FramebufferRegs::BlendFactor::OneMinusSourceColor:
                         return 255 - combiner_output[channel];
 
-                    case Regs::BlendFactor::DestColor:
+                    case FramebufferRegs::BlendFactor::DestColor:
                         return dest[channel];
 
-                    case Regs::BlendFactor::OneMinusDestColor:
+                    case FramebufferRegs::BlendFactor::OneMinusDestColor:
                         return 255 - dest[channel];
 
-                    case Regs::BlendFactor::SourceAlpha:
+                    case FramebufferRegs::BlendFactor::SourceAlpha:
                         return combiner_output.a();
 
-                    case Regs::BlendFactor::OneMinusSourceAlpha:
+                    case FramebufferRegs::BlendFactor::OneMinusSourceAlpha:
                         return 255 - combiner_output.a();
 
-                    case Regs::BlendFactor::DestAlpha:
+                    case FramebufferRegs::BlendFactor::DestAlpha:
                         return dest.a();
 
-                    case Regs::BlendFactor::OneMinusDestAlpha:
+                    case FramebufferRegs::BlendFactor::OneMinusDestAlpha:
                         return 255 - dest.a();
 
-                    case Regs::BlendFactor::ConstantColor:
+                    case FramebufferRegs::BlendFactor::ConstantColor:
                         return blend_const[channel];
 
-                    case Regs::BlendFactor::OneMinusConstantColor:
+                    case FramebufferRegs::BlendFactor::OneMinusConstantColor:
                         return 255 - blend_const[channel];
 
-                    case Regs::BlendFactor::ConstantAlpha:
+                    case FramebufferRegs::BlendFactor::ConstantAlpha:
                         return blend_const.a();
 
-                    case Regs::BlendFactor::OneMinusConstantAlpha:
+                    case FramebufferRegs::BlendFactor::OneMinusConstantAlpha:
                         return 255 - blend_const.a();
 
-                    case Regs::BlendFactor::SourceAlphaSaturate:
+                    case FramebufferRegs::BlendFactor::SourceAlphaSaturate:
                         // Returns 1.0 for the alpha channel
                         if (channel == 3)
                             return 255;
@@ -1143,36 +1153,37 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
                 static auto EvaluateBlendEquation = [](
                     const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor,
                     const Math::Vec4<u8>& dest, const Math::Vec4<u8>& destfactor,
-                    Regs::BlendEquation equation) {
+                    FramebufferRegs::BlendEquation equation) {
+
                     Math::Vec4<int> result;
 
                     auto src_result = (src * srcfactor).Cast<int>();
                     auto dst_result = (dest * destfactor).Cast<int>();
 
                     switch (equation) {
-                    case Regs::BlendEquation::Add:
+                    case FramebufferRegs::BlendEquation::Add:
                         result = (src_result + dst_result) / 255;
                         break;
 
-                    case Regs::BlendEquation::Subtract:
+                    case FramebufferRegs::BlendEquation::Subtract:
                         result = (src_result - dst_result) / 255;
                         break;
 
-                    case Regs::BlendEquation::ReverseSubtract:
+                    case FramebufferRegs::BlendEquation::ReverseSubtract:
                         result = (dst_result - src_result) / 255;
                         break;
 
                     // TODO: How do these two actually work?
                     //       OpenGL doesn't include the blend factors in the min/max computations,
                     //       but is this what the 3DS actually does?
-                    case Regs::BlendEquation::Min:
+                    case FramebufferRegs::BlendEquation::Min:
                         result.r() = std::min(src.r(), dest.r());
                         result.g() = std::min(src.g(), dest.g());
                         result.b() = std::min(src.b(), dest.b());
                         result.a() = std::min(src.a(), dest.a());
                         break;
 
-                    case Regs::BlendEquation::Max:
+                    case FramebufferRegs::BlendEquation::Max:
                         result.r() = std::max(src.r(), dest.r());
                         result.g() = std::max(src.g(), dest.g());
                         result.b() = std::max(src.b(), dest.b());
@@ -1205,54 +1216,54 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
                                                          dstfactor, params.blend_equation_a)
                                        .a();
             } else {
-                static auto LogicOp = [](u8 src, u8 dest, Regs::LogicOp op) -> u8 {
+                static auto LogicOp = [](u8 src, u8 dest, FramebufferRegs::LogicOp op) -> u8 {
                     switch (op) {
-                    case Regs::LogicOp::Clear:
+                    case FramebufferRegs::LogicOp::Clear:
                         return 0;
 
-                    case Regs::LogicOp::And:
+                    case FramebufferRegs::LogicOp::And:
                         return src & dest;
 
-                    case Regs::LogicOp::AndReverse:
+                    case FramebufferRegs::LogicOp::AndReverse:
                         return src & ~dest;
 
-                    case Regs::LogicOp::Copy:
+                    case FramebufferRegs::LogicOp::Copy:
                         return src;
 
-                    case Regs::LogicOp::Set:
+                    case FramebufferRegs::LogicOp::Set:
                         return 255;
 
-                    case Regs::LogicOp::CopyInverted:
+                    case FramebufferRegs::LogicOp::CopyInverted:
                         return ~src;
 
-                    case Regs::LogicOp::NoOp:
+                    case FramebufferRegs::LogicOp::NoOp:
                         return dest;
 
-                    case Regs::LogicOp::Invert:
+                    case FramebufferRegs::LogicOp::Invert:
                         return ~dest;
 
-                    case Regs::LogicOp::Nand:
+                    case FramebufferRegs::LogicOp::Nand:
                         return ~(src & dest);
 
-                    case Regs::LogicOp::Or:
+                    case FramebufferRegs::LogicOp::Or:
                         return src | dest;
 
-                    case Regs::LogicOp::Nor:
+                    case FramebufferRegs::LogicOp::Nor:
                         return ~(src | dest);
 
-                    case Regs::LogicOp::Xor:
+                    case FramebufferRegs::LogicOp::Xor:
                         return src ^ dest;
 
-                    case Regs::LogicOp::Equiv:
+                    case FramebufferRegs::LogicOp::Equiv:
                         return ~(src ^ dest);
 
-                    case Regs::LogicOp::AndInverted:
+                    case FramebufferRegs::LogicOp::AndInverted:
                         return ~src & dest;
 
-                    case Regs::LogicOp::OrReverse:
+                    case FramebufferRegs::LogicOp::OrReverse:
                         return src | ~dest;
 
-                    case Regs::LogicOp::OrInverted:
+                    case FramebufferRegs::LogicOp::OrInverted:
                         return ~src | dest;
                     }
                 };
@@ -1271,7 +1282,7 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
                 output_merger.alpha_enable ? blend_output.a() : dest.a(),
             };
 
-            if (regs.framebuffer.allow_color_write != 0)
+            if (regs.framebuffer.framebuffer.allow_color_write != 0)
                 DrawPixel(x >> 4, y >> 4, result);
         }
     }
diff --git a/src/video_core/regs.cpp b/src/video_core/regs.cpp
new file mode 100644
index 0000000000..f47e9e7634
--- /dev/null
+++ b/src/video_core/regs.cpp
@@ -0,0 +1,493 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <iterator>
+#include <unordered_map>
+#include <utility>
+
+#include "common/common_types.h"
+#include "video_core/regs.h"
+
+namespace Pica {
+
+static const std::pair<u16, const char*> register_names[] = {
+    {0x010, "GPUREG_FINALIZE"},
+
+    {0x040, "GPUREG_FACECULLING_CONFIG"},
+    {0x041, "GPUREG_VIEWPORT_WIDTH"},
+    {0x042, "GPUREG_VIEWPORT_INVW"},
+    {0x043, "GPUREG_VIEWPORT_HEIGHT"},
+    {0x044, "GPUREG_VIEWPORT_INVH"},
+
+    {0x047, "GPUREG_FRAGOP_CLIP"},
+    {0x048, "GPUREG_FRAGOP_CLIP_DATA0"},
+    {0x049, "GPUREG_FRAGOP_CLIP_DATA1"},
+    {0x04A, "GPUREG_FRAGOP_CLIP_DATA2"},
+    {0x04B, "GPUREG_FRAGOP_CLIP_DATA3"},
+
+    {0x04D, "GPUREG_DEPTHMAP_SCALE"},
+    {0x04E, "GPUREG_DEPTHMAP_OFFSET"},
+    {0x04F, "GPUREG_SH_OUTMAP_TOTAL"},
+    {0x050, "GPUREG_SH_OUTMAP_O0"},
+    {0x051, "GPUREG_SH_OUTMAP_O1"},
+    {0x052, "GPUREG_SH_OUTMAP_O2"},
+    {0x053, "GPUREG_SH_OUTMAP_O3"},
+    {0x054, "GPUREG_SH_OUTMAP_O4"},
+    {0x055, "GPUREG_SH_OUTMAP_O5"},
+    {0x056, "GPUREG_SH_OUTMAP_O6"},
+
+    {0x061, "GPUREG_EARLYDEPTH_FUNC"},
+    {0x062, "GPUREG_EARLYDEPTH_TEST1"},
+    {0x063, "GPUREG_EARLYDEPTH_CLEAR"},
+    {0x064, "GPUREG_SH_OUTATTR_MODE"},
+    {0x065, "GPUREG_SCISSORTEST_MODE"},
+    {0x066, "GPUREG_SCISSORTEST_POS"},
+    {0x067, "GPUREG_SCISSORTEST_DIM"},
+    {0x068, "GPUREG_VIEWPORT_XY"},
+
+    {0x06A, "GPUREG_EARLYDEPTH_DATA"},
+
+    {0x06D, "GPUREG_DEPTHMAP_ENABLE"},
+    {0x06E, "GPUREG_RENDERBUF_DIM"},
+    {0x06F, "GPUREG_SH_OUTATTR_CLOCK"},
+
+    {0x080, "GPUREG_TEXUNIT_CONFIG"},
+    {0x081, "GPUREG_TEXUNIT0_BORDER_COLOR"},
+    {0x082, "GPUREG_TEXUNIT0_DIM"},
+    {0x083, "GPUREG_TEXUNIT0_PARAM"},
+    {0x084, "GPUREG_TEXUNIT0_LOD"},
+    {0x085, "GPUREG_TEXUNIT0_ADDR1"},
+    {0x086, "GPUREG_TEXUNIT0_ADDR2"},
+    {0x087, "GPUREG_TEXUNIT0_ADDR3"},
+    {0x088, "GPUREG_TEXUNIT0_ADDR4"},
+    {0x089, "GPUREG_TEXUNIT0_ADDR5"},
+    {0x08A, "GPUREG_TEXUNIT0_ADDR6"},
+    {0x08B, "GPUREG_TEXUNIT0_SHADOW"},
+
+    {0x08E, "GPUREG_TEXUNIT0_TYPE"},
+    {0x08F, "GPUREG_LIGHTING_ENABLE0"},
+
+    {0x091, "GPUREG_TEXUNIT1_BORDER_COLOR"},
+    {0x092, "GPUREG_TEXUNIT1_DIM"},
+    {0x093, "GPUREG_TEXUNIT1_PARAM"},
+    {0x094, "GPUREG_TEXUNIT1_LOD"},
+    {0x095, "GPUREG_TEXUNIT1_ADDR"},
+    {0x096, "GPUREG_TEXUNIT1_TYPE"},
+
+    {0x099, "GPUREG_TEXUNIT2_BORDER_COLOR"},
+    {0x09A, "GPUREG_TEXUNIT2_DIM"},
+    {0x09B, "GPUREG_TEXUNIT2_PARAM"},
+    {0x09C, "GPUREG_TEXUNIT2_LOD"},
+    {0x09D, "GPUREG_TEXUNIT2_ADDR"},
+    {0x09E, "GPUREG_TEXUNIT2_TYPE"},
+
+    {0x0A8, "GPUREG_TEXUNIT3_PROCTEX0"},
+    {0x0A9, "GPUREG_TEXUNIT3_PROCTEX1"},
+    {0x0AA, "GPUREG_TEXUNIT3_PROCTEX2"},
+    {0x0AB, "GPUREG_TEXUNIT3_PROCTEX3"},
+    {0x0AC, "GPUREG_TEXUNIT3_PROCTEX4"},
+    {0x0AD, "GPUREG_TEXUNIT3_PROCTEX5"},
+
+    {0x0AF, "GPUREG_PROCTEX_LUT"},
+    {0x0B0, "GPUREG_PROCTEX_LUT_DATA0"},
+    {0x0B1, "GPUREG_PROCTEX_LUT_DATA1"},
+    {0x0B2, "GPUREG_PROCTEX_LUT_DATA2"},
+    {0x0B3, "GPUREG_PROCTEX_LUT_DATA3"},
+    {0x0B4, "GPUREG_PROCTEX_LUT_DATA4"},
+    {0x0B5, "GPUREG_PROCTEX_LUT_DATA5"},
+    {0x0B6, "GPUREG_PROCTEX_LUT_DATA6"},
+    {0x0B7, "GPUREG_PROCTEX_LUT_DATA7"},
+
+    {0x0C0, "GPUREG_TEXENV0_SOURCE"},
+    {0x0C1, "GPUREG_TEXENV0_OPERAND"},
+    {0x0C2, "GPUREG_TEXENV0_COMBINER"},
+    {0x0C3, "GPUREG_TEXENV0_COLOR"},
+    {0x0C4, "GPUREG_TEXENV0_SCALE"},
+
+    {0x0C8, "GPUREG_TEXENV1_SOURCE"},
+    {0x0C9, "GPUREG_TEXENV1_OPERAND"},
+    {0x0CA, "GPUREG_TEXENV1_COMBINER"},
+    {0x0CB, "GPUREG_TEXENV1_COLOR"},
+    {0x0CC, "GPUREG_TEXENV1_SCALE"},
+
+    {0x0D0, "GPUREG_TEXENV2_SOURCE"},
+    {0x0D1, "GPUREG_TEXENV2_OPERAND"},
+    {0x0D2, "GPUREG_TEXENV2_COMBINER"},
+    {0x0D3, "GPUREG_TEXENV2_COLOR"},
+    {0x0D4, "GPUREG_TEXENV2_SCALE"},
+
+    {0x0D8, "GPUREG_TEXENV3_SOURCE"},
+    {0x0D9, "GPUREG_TEXENV3_OPERAND"},
+    {0x0DA, "GPUREG_TEXENV3_COMBINER"},
+    {0x0DB, "GPUREG_TEXENV3_COLOR"},
+    {0x0DC, "GPUREG_TEXENV3_SCALE"},
+
+    {0x0E0, "GPUREG_TEXENV_UPDATE_BUFFER"},
+    {0x0E1, "GPUREG_FOG_COLOR"},
+
+    {0x0E4, "GPUREG_GAS_ATTENUATION"},
+    {0x0E5, "GPUREG_GAS_ACCMAX"},
+    {0x0E6, "GPUREG_FOG_LUT_INDEX"},
+
+    {0x0E8, "GPUREG_FOG_LUT_DATA0"},
+    {0x0E9, "GPUREG_FOG_LUT_DATA1"},
+    {0x0EA, "GPUREG_FOG_LUT_DATA2"},
+    {0x0EB, "GPUREG_FOG_LUT_DATA3"},
+    {0x0EC, "GPUREG_FOG_LUT_DATA4"},
+    {0x0ED, "GPUREG_FOG_LUT_DATA5"},
+    {0x0EE, "GPUREG_FOG_LUT_DATA6"},
+    {0x0EF, "GPUREG_FOG_LUT_DATA7"},
+    {0x0F0, "GPUREG_TEXENV4_SOURCE"},
+    {0x0F1, "GPUREG_TEXENV4_OPERAND"},
+    {0x0F2, "GPUREG_TEXENV4_COMBINER"},
+    {0x0F3, "GPUREG_TEXENV4_COLOR"},
+    {0x0F4, "GPUREG_TEXENV4_SCALE"},
+
+    {0x0F8, "GPUREG_TEXENV5_SOURCE"},
+    {0x0F9, "GPUREG_TEXENV5_OPERAND"},
+    {0x0FA, "GPUREG_TEXENV5_COMBINER"},
+    {0x0FB, "GPUREG_TEXENV5_COLOR"},
+    {0x0FC, "GPUREG_TEXENV5_SCALE"},
+    {0x0FD, "GPUREG_TEXENV_BUFFER_COLOR"},
+
+    {0x100, "GPUREG_COLOR_OPERATION"},
+    {0x101, "GPUREG_BLEND_FUNC"},
+    {0x102, "GPUREG_LOGIC_OP"},
+    {0x103, "GPUREG_BLEND_COLOR"},
+    {0x104, "GPUREG_FRAGOP_ALPHA_TEST"},
+    {0x105, "GPUREG_STENCIL_TEST"},
+    {0x106, "GPUREG_STENCIL_OP"},
+    {0x107, "GPUREG_DEPTH_COLOR_MASK"},
+
+    {0x110, "GPUREG_FRAMEBUFFER_INVALIDATE"},
+    {0x111, "GPUREG_FRAMEBUFFER_FLUSH"},
+    {0x112, "GPUREG_COLORBUFFER_READ"},
+    {0x113, "GPUREG_COLORBUFFER_WRITE"},
+    {0x114, "GPUREG_DEPTHBUFFER_READ"},
+    {0x115, "GPUREG_DEPTHBUFFER_WRITE"},
+    {0x116, "GPUREG_DEPTHBUFFER_FORMAT"},
+    {0x117, "GPUREG_COLORBUFFER_FORMAT"},
+    {0x118, "GPUREG_EARLYDEPTH_TEST2"},
+
+    {0x11B, "GPUREG_FRAMEBUFFER_BLOCK32"},
+    {0x11C, "GPUREG_DEPTHBUFFER_LOC"},
+    {0x11D, "GPUREG_COLORBUFFER_LOC"},
+    {0x11E, "GPUREG_FRAMEBUFFER_DIM"},
+
+    {0x120, "GPUREG_GAS_LIGHT_XY"},
+    {0x121, "GPUREG_GAS_LIGHT_Z"},
+    {0x122, "GPUREG_GAS_LIGHT_Z_COLOR"},
+    {0x123, "GPUREG_GAS_LUT_INDEX"},
+    {0x124, "GPUREG_GAS_LUT_DATA"},
+
+    {0x126, "GPUREG_GAS_DELTAZ_DEPTH"},
+
+    {0x130, "GPUREG_FRAGOP_SHADOW"},
+
+    {0x140, "GPUREG_LIGHT0_SPECULAR0"},
+    {0x141, "GPUREG_LIGHT0_SPECULAR1"},
+    {0x142, "GPUREG_LIGHT0_DIFFUSE"},
+    {0x143, "GPUREG_LIGHT0_AMBIENT"},
+    {0x144, "GPUREG_LIGHT0_XY"},
+    {0x145, "GPUREG_LIGHT0_Z"},
+    {0x146, "GPUREG_LIGHT0_SPOTDIR_XY"},
+    {0x147, "GPUREG_LIGHT0_SPOTDIR_Z"},
+
+    {0x149, "GPUREG_LIGHT0_CONFIG"},
+    {0x14A, "GPUREG_LIGHT0_ATTENUATION_BIAS"},
+    {0x14B, "GPUREG_LIGHT0_ATTENUATION_SCALE"},
+
+    {0x150, "GPUREG_LIGHT1_SPECULAR0"},
+    {0x151, "GPUREG_LIGHT1_SPECULAR1"},
+    {0x152, "GPUREG_LIGHT1_DIFFUSE"},
+    {0x153, "GPUREG_LIGHT1_AMBIENT"},
+    {0x154, "GPUREG_LIGHT1_XY"},
+    {0x155, "GPUREG_LIGHT1_Z"},
+    {0x156, "GPUREG_LIGHT1_SPOTDIR_XY"},
+    {0x157, "GPUREG_LIGHT1_SPOTDIR_Z"},
+
+    {0x159, "GPUREG_LIGHT1_CONFIG"},
+    {0x15A, "GPUREG_LIGHT1_ATTENUATION_BIAS"},
+    {0x15B, "GPUREG_LIGHT1_ATTENUATION_SCALE"},
+
+    {0x160, "GPUREG_LIGHT2_SPECULAR0"},
+    {0x161, "GPUREG_LIGHT2_SPECULAR1"},
+    {0x162, "GPUREG_LIGHT2_DIFFUSE"},
+    {0x163, "GPUREG_LIGHT2_AMBIENT"},
+    {0x164, "GPUREG_LIGHT2_XY"},
+    {0x165, "GPUREG_LIGHT2_Z"},
+    {0x166, "GPUREG_LIGHT2_SPOTDIR_XY"},
+    {0x167, "GPUREG_LIGHT2_SPOTDIR_Z"},
+
+    {0x169, "GPUREG_LIGHT2_CONFIG"},
+    {0x16A, "GPUREG_LIGHT2_ATTENUATION_BIAS"},
+    {0x16B, "GPUREG_LIGHT2_ATTENUATION_SCALE"},
+
+    {0x170, "GPUREG_LIGHT3_SPECULAR0"},
+    {0x171, "GPUREG_LIGHT3_SPECULAR1"},
+    {0x172, "GPUREG_LIGHT3_DIFFUSE"},
+    {0x173, "GPUREG_LIGHT3_AMBIENT"},
+    {0x174, "GPUREG_LIGHT3_XY"},
+    {0x175, "GPUREG_LIGHT3_Z"},
+    {0x176, "GPUREG_LIGHT3_SPOTDIR_XY"},
+    {0x177, "GPUREG_LIGHT3_SPOTDIR_Z"},
+
+    {0x179, "GPUREG_LIGHT3_CONFIG"},
+    {0x17A, "GPUREG_LIGHT3_ATTENUATION_BIAS"},
+    {0x17B, "GPUREG_LIGHT3_ATTENUATION_SCALE"},
+
+    {0x180, "GPUREG_LIGHT4_SPECULAR0"},
+    {0x181, "GPUREG_LIGHT4_SPECULAR1"},
+    {0x182, "GPUREG_LIGHT4_DIFFUSE"},
+    {0x183, "GPUREG_LIGHT4_AMBIENT"},
+    {0x184, "GPUREG_LIGHT4_XY"},
+    {0x185, "GPUREG_LIGHT4_Z"},
+    {0x186, "GPUREG_LIGHT4_SPOTDIR_XY"},
+    {0x187, "GPUREG_LIGHT4_SPOTDIR_Z"},
+
+    {0x189, "GPUREG_LIGHT4_CONFIG"},
+    {0x18A, "GPUREG_LIGHT4_ATTENUATION_BIAS"},
+    {0x18B, "GPUREG_LIGHT4_ATTENUATION_SCALE"},
+
+    {0x190, "GPUREG_LIGHT5_SPECULAR0"},
+    {0x191, "GPUREG_LIGHT5_SPECULAR1"},
+    {0x192, "GPUREG_LIGHT5_DIFFUSE"},
+    {0x193, "GPUREG_LIGHT5_AMBIENT"},
+    {0x194, "GPUREG_LIGHT5_XY"},
+    {0x195, "GPUREG_LIGHT5_Z"},
+    {0x196, "GPUREG_LIGHT5_SPOTDIR_XY"},
+    {0x197, "GPUREG_LIGHT5_SPOTDIR_Z"},
+
+    {0x199, "GPUREG_LIGHT5_CONFIG"},
+    {0x19A, "GPUREG_LIGHT5_ATTENUATION_BIAS"},
+    {0x19B, "GPUREG_LIGHT5_ATTENUATION_SCALE"},
+
+    {0x1A0, "GPUREG_LIGHT6_SPECULAR0"},
+    {0x1A1, "GPUREG_LIGHT6_SPECULAR1"},
+    {0x1A2, "GPUREG_LIGHT6_DIFFUSE"},
+    {0x1A3, "GPUREG_LIGHT6_AMBIENT"},
+    {0x1A4, "GPUREG_LIGHT6_XY"},
+    {0x1A5, "GPUREG_LIGHT6_Z"},
+    {0x1A6, "GPUREG_LIGHT6_SPOTDIR_XY"},
+    {0x1A7, "GPUREG_LIGHT6_SPOTDIR_Z"},
+
+    {0x1A9, "GPUREG_LIGHT6_CONFIG"},
+    {0x1AA, "GPUREG_LIGHT6_ATTENUATION_BIAS"},
+    {0x1AB, "GPUREG_LIGHT6_ATTENUATION_SCALE"},
+
+    {0x1B0, "GPUREG_LIGHT7_SPECULAR0"},
+    {0x1B1, "GPUREG_LIGHT7_SPECULAR1"},
+    {0x1B2, "GPUREG_LIGHT7_DIFFUSE"},
+    {0x1B3, "GPUREG_LIGHT7_AMBIENT"},
+    {0x1B4, "GPUREG_LIGHT7_XY"},
+    {0x1B5, "GPUREG_LIGHT7_Z"},
+    {0x1B6, "GPUREG_LIGHT7_SPOTDIR_XY"},
+    {0x1B7, "GPUREG_LIGHT7_SPOTDIR_Z"},
+
+    {0x1B9, "GPUREG_LIGHT7_CONFIG"},
+    {0x1BA, "GPUREG_LIGHT7_ATTENUATION_BIAS"},
+    {0x1BB, "GPUREG_LIGHT7_ATTENUATION_SCALE"},
+
+    {0x1C0, "GPUREG_LIGHTING_AMBIENT"},
+
+    {0x1C2, "GPUREG_LIGHTING_NUM_LIGHTS"},
+    {0x1C3, "GPUREG_LIGHTING_CONFIG0"},
+    {0x1C4, "GPUREG_LIGHTING_CONFIG1"},
+    {0x1C5, "GPUREG_LIGHTING_LUT_INDEX"},
+    {0x1C6, "GPUREG_LIGHTING_ENABLE1"},
+
+    {0x1C8, "GPUREG_LIGHTING_LUT_DATA0"},
+    {0x1C9, "GPUREG_LIGHTING_LUT_DATA1"},
+    {0x1CA, "GPUREG_LIGHTING_LUT_DATA2"},
+    {0x1CB, "GPUREG_LIGHTING_LUT_DATA3"},
+    {0x1CC, "GPUREG_LIGHTING_LUT_DATA4"},
+    {0x1CD, "GPUREG_LIGHTING_LUT_DATA5"},
+    {0x1CE, "GPUREG_LIGHTING_LUT_DATA6"},
+    {0x1CF, "GPUREG_LIGHTING_LUT_DATA7"},
+    {0x1D0, "GPUREG_LIGHTING_LUTINPUT_ABS"},
+    {0x1D1, "GPUREG_LIGHTING_LUTINPUT_SELECT"},
+    {0x1D2, "GPUREG_LIGHTING_LUTINPUT_SCALE"},
+
+    {0x1D9, "GPUREG_LIGHTING_LIGHT_PERMUTATION"},
+
+    {0x200, "GPUREG_ATTRIBBUFFERS_LOC"},
+    {0x201, "GPUREG_ATTRIBBUFFERS_FORMAT_LOW"},
+    {0x202, "GPUREG_ATTRIBBUFFERS_FORMAT_HIGH"},
+    {0x203, "GPUREG_ATTRIBBUFFER0_OFFSET"},
+    {0x204, "GPUREG_ATTRIBBUFFER0_CONFIG1"},
+    {0x205, "GPUREG_ATTRIBBUFFER0_CONFIG2"},
+    {0x206, "GPUREG_ATTRIBBUFFER1_OFFSET"},
+    {0x207, "GPUREG_ATTRIBBUFFER1_CONFIG1"},
+    {0x208, "GPUREG_ATTRIBBUFFER1_CONFIG2"},
+    {0x209, "GPUREG_ATTRIBBUFFER2_OFFSET"},
+    {0x20A, "GPUREG_ATTRIBBUFFER2_CONFIG1"},
+    {0x20B, "GPUREG_ATTRIBBUFFER2_CONFIG2"},
+    {0x20C, "GPUREG_ATTRIBBUFFER3_OFFSET"},
+    {0x20D, "GPUREG_ATTRIBBUFFER3_CONFIG1"},
+    {0x20E, "GPUREG_ATTRIBBUFFER3_CONFIG2"},
+    {0x20F, "GPUREG_ATTRIBBUFFER4_OFFSET"},
+    {0x210, "GPUREG_ATTRIBBUFFER4_CONFIG1"},
+    {0x211, "GPUREG_ATTRIBBUFFER4_CONFIG2"},
+    {0x212, "GPUREG_ATTRIBBUFFER5_OFFSET"},
+    {0x213, "GPUREG_ATTRIBBUFFER5_CONFIG1"},
+    {0x214, "GPUREG_ATTRIBBUFFER5_CONFIG2"},
+    {0x215, "GPUREG_ATTRIBBUFFER6_OFFSET"},
+    {0x216, "GPUREG_ATTRIBBUFFER6_CONFIG1"},
+    {0x217, "GPUREG_ATTRIBBUFFER6_CONFIG2"},
+    {0x218, "GPUREG_ATTRIBBUFFER7_OFFSET"},
+    {0x219, "GPUREG_ATTRIBBUFFER7_CONFIG1"},
+    {0x21A, "GPUREG_ATTRIBBUFFER7_CONFIG2"},
+    {0x21B, "GPUREG_ATTRIBBUFFER8_OFFSET"},
+    {0x21C, "GPUREG_ATTRIBBUFFER8_CONFIG1"},
+    {0x21D, "GPUREG_ATTRIBBUFFER8_CONFIG2"},
+    {0x21E, "GPUREG_ATTRIBBUFFER9_OFFSET"},
+    {0x21F, "GPUREG_ATTRIBBUFFER9_CONFIG1"},
+    {0x220, "GPUREG_ATTRIBBUFFER9_CONFIG2"},
+    {0x221, "GPUREG_ATTRIBBUFFER10_OFFSET"},
+    {0x222, "GPUREG_ATTRIBBUFFER10_CONFIG1"},
+    {0x223, "GPUREG_ATTRIBBUFFER10_CONFIG2"},
+    {0x224, "GPUREG_ATTRIBBUFFER11_OFFSET"},
+    {0x225, "GPUREG_ATTRIBBUFFER11_CONFIG1"},
+    {0x226, "GPUREG_ATTRIBBUFFER11_CONFIG2"},
+    {0x227, "GPUREG_INDEXBUFFER_CONFIG"},
+    {0x228, "GPUREG_NUMVERTICES"},
+    {0x229, "GPUREG_GEOSTAGE_CONFIG"},
+    {0x22A, "GPUREG_VERTEX_OFFSET"},
+
+    {0x22D, "GPUREG_POST_VERTEX_CACHE_NUM"},
+    {0x22E, "GPUREG_DRAWARRAYS"},
+    {0x22F, "GPUREG_DRAWELEMENTS"},
+
+    {0x231, "GPUREG_VTX_FUNC"},
+    {0x232, "GPUREG_FIXEDATTRIB_INDEX"},
+    {0x233, "GPUREG_FIXEDATTRIB_DATA0"},
+    {0x234, "GPUREG_FIXEDATTRIB_DATA1"},
+    {0x235, "GPUREG_FIXEDATTRIB_DATA2"},
+
+    {0x238, "GPUREG_CMDBUF_SIZE0"},
+    {0x239, "GPUREG_CMDBUF_SIZE1"},
+    {0x23A, "GPUREG_CMDBUF_ADDR0"},
+    {0x23B, "GPUREG_CMDBUF_ADDR1"},
+    {0x23C, "GPUREG_CMDBUF_JUMP0"},
+    {0x23D, "GPUREG_CMDBUF_JUMP1"},
+
+    {0x242, "GPUREG_VSH_NUM_ATTR"},
+
+    {0x244, "GPUREG_VSH_COM_MODE"},
+    {0x245, "GPUREG_START_DRAW_FUNC0"},
+
+    {0x24A, "GPUREG_VSH_OUTMAP_TOTAL1"},
+
+    {0x251, "GPUREG_VSH_OUTMAP_TOTAL2"},
+    {0x252, "GPUREG_GSH_MISC0"},
+    {0x253, "GPUREG_GEOSTAGE_CONFIG2"},
+    {0x254, "GPUREG_GSH_MISC1"},
+
+    {0x25E, "GPUREG_PRIMITIVE_CONFIG"},
+    {0x25F, "GPUREG_RESTART_PRIMITIVE"},
+
+    {0x280, "GPUREG_GSH_BOOLUNIFORM"},
+    {0x281, "GPUREG_GSH_INTUNIFORM_I0"},
+    {0x282, "GPUREG_GSH_INTUNIFORM_I1"},
+    {0x283, "GPUREG_GSH_INTUNIFORM_I2"},
+    {0x284, "GPUREG_GSH_INTUNIFORM_I3"},
+
+    {0x289, "GPUREG_GSH_INPUTBUFFER_CONFIG"},
+    {0x28A, "GPUREG_GSH_ENTRYPOINT"},
+    {0x28B, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_LOW"},
+    {0x28C, "GPUREG_GSH_ATTRIBUTES_PERMUTATION_HIGH"},
+    {0x28D, "GPUREG_GSH_OUTMAP_MASK"},
+
+    {0x28F, "GPUREG_GSH_CODETRANSFER_END"},
+    {0x290, "GPUREG_GSH_FLOATUNIFORM_INDEX"},
+    {0x291, "GPUREG_GSH_FLOATUNIFORM_DATA0"},
+    {0x292, "GPUREG_GSH_FLOATUNIFORM_DATA1"},
+    {0x293, "GPUREG_GSH_FLOATUNIFORM_DATA2"},
+    {0x294, "GPUREG_GSH_FLOATUNIFORM_DATA3"},
+    {0x295, "GPUREG_GSH_FLOATUNIFORM_DATA4"},
+    {0x296, "GPUREG_GSH_FLOATUNIFORM_DATA5"},
+    {0x297, "GPUREG_GSH_FLOATUNIFORM_DATA6"},
+    {0x298, "GPUREG_GSH_FLOATUNIFORM_DATA7"},
+
+    {0x29B, "GPUREG_GSH_CODETRANSFER_INDEX"},
+    {0x29C, "GPUREG_GSH_CODETRANSFER_DATA0"},
+    {0x29D, "GPUREG_GSH_CODETRANSFER_DATA1"},
+    {0x29E, "GPUREG_GSH_CODETRANSFER_DATA2"},
+    {0x29F, "GPUREG_GSH_CODETRANSFER_DATA3"},
+    {0x2A0, "GPUREG_GSH_CODETRANSFER_DATA4"},
+    {0x2A1, "GPUREG_GSH_CODETRANSFER_DATA5"},
+    {0x2A2, "GPUREG_GSH_CODETRANSFER_DATA6"},
+    {0x2A3, "GPUREG_GSH_CODETRANSFER_DATA7"},
+
+    {0x2A5, "GPUREG_GSH_OPDESCS_INDEX"},
+    {0x2A6, "GPUREG_GSH_OPDESCS_DATA0"},
+    {0x2A7, "GPUREG_GSH_OPDESCS_DATA1"},
+    {0x2A8, "GPUREG_GSH_OPDESCS_DATA2"},
+    {0x2A9, "GPUREG_GSH_OPDESCS_DATA3"},
+    {0x2AA, "GPUREG_GSH_OPDESCS_DATA4"},
+    {0x2AB, "GPUREG_GSH_OPDESCS_DATA5"},
+    {0x2AC, "GPUREG_GSH_OPDESCS_DATA6"},
+    {0x2AD, "GPUREG_GSH_OPDESCS_DATA7"},
+
+    {0x2B0, "GPUREG_VSH_BOOLUNIFORM"},
+    {0x2B1, "GPUREG_VSH_INTUNIFORM_I0"},
+    {0x2B2, "GPUREG_VSH_INTUNIFORM_I1"},
+    {0x2B3, "GPUREG_VSH_INTUNIFORM_I2"},
+    {0x2B4, "GPUREG_VSH_INTUNIFORM_I3"},
+
+    {0x2B9, "GPUREG_VSH_INPUTBUFFER_CONFIG"},
+    {0x2BA, "GPUREG_VSH_ENTRYPOINT"},
+    {0x2BB, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_LOW"},
+    {0x2BC, "GPUREG_VSH_ATTRIBUTES_PERMUTATION_HIGH"},
+    {0x2BD, "GPUREG_VSH_OUTMAP_MASK"},
+
+    {0x2BF, "GPUREG_VSH_CODETRANSFER_END"},
+    {0x2C0, "GPUREG_VSH_FLOATUNIFORM_INDEX"},
+    {0x2C1, "GPUREG_VSH_FLOATUNIFORM_DATA0"},
+    {0x2C2, "GPUREG_VSH_FLOATUNIFORM_DATA1"},
+    {0x2C3, "GPUREG_VSH_FLOATUNIFORM_DATA2"},
+    {0x2C4, "GPUREG_VSH_FLOATUNIFORM_DATA3"},
+    {0x2C5, "GPUREG_VSH_FLOATUNIFORM_DATA4"},
+    {0x2C6, "GPUREG_VSH_FLOATUNIFORM_DATA5"},
+    {0x2C7, "GPUREG_VSH_FLOATUNIFORM_DATA6"},
+    {0x2C8, "GPUREG_VSH_FLOATUNIFORM_DATA7"},
+
+    {0x2CB, "GPUREG_VSH_CODETRANSFER_INDEX"},
+    {0x2CC, "GPUREG_VSH_CODETRANSFER_DATA0"},
+    {0x2CD, "GPUREG_VSH_CODETRANSFER_DATA1"},
+    {0x2CE, "GPUREG_VSH_CODETRANSFER_DATA2"},
+    {0x2CF, "GPUREG_VSH_CODETRANSFER_DATA3"},
+    {0x2D0, "GPUREG_VSH_CODETRANSFER_DATA4"},
+    {0x2D1, "GPUREG_VSH_CODETRANSFER_DATA5"},
+    {0x2D2, "GPUREG_VSH_CODETRANSFER_DATA6"},
+    {0x2D3, "GPUREG_VSH_CODETRANSFER_DATA7"},
+
+    {0x2D5, "GPUREG_VSH_OPDESCS_INDEX"},
+    {0x2D6, "GPUREG_VSH_OPDESCS_DATA0"},
+    {0x2D7, "GPUREG_VSH_OPDESCS_DATA1"},
+    {0x2D8, "GPUREG_VSH_OPDESCS_DATA2"},
+    {0x2D9, "GPUREG_VSH_OPDESCS_DATA3"},
+    {0x2DA, "GPUREG_VSH_OPDESCS_DATA4"},
+    {0x2DB, "GPUREG_VSH_OPDESCS_DATA5"},
+    {0x2DC, "GPUREG_VSH_OPDESCS_DATA6"},
+    {0x2DD, "GPUREG_VSH_OPDESCS_DATA7"},
+};
+
+std::string Regs::GetCommandName(int index) {
+    static std::unordered_map<u32, const char*> map;
+
+    if (map.empty()) {
+        map.insert(std::begin(register_names), std::end(register_names));
+    }
+
+    // Return empty string if no match is found
+    auto it = map.find(index);
+    if (it != map.end()) {
+        return it->second;
+    } else {
+        return std::string();
+    }
+}
+
+} // namespace Pica
diff --git a/src/video_core/regs.h b/src/video_core/regs.h
new file mode 100644
index 0000000000..f25edde27e
--- /dev/null
+++ b/src/video_core/regs.h
@@ -0,0 +1,164 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <string>
+#ifndef _MSC_VER
+#include <type_traits> // for std::enable_if
+#endif
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/regs_framebuffer.h"
+#include "video_core/regs_lighting.h"
+#include "video_core/regs_pipeline.h"
+#include "video_core/regs_rasterizer.h"
+#include "video_core/regs_shader.h"
+#include "video_core/regs_texturing.h"
+
+namespace Pica {
+
+// Returns index corresponding to the Regs member labeled by field_name
+// TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions
+//       when used with array elements (e.g. PICA_REG_INDEX(vs_uniform_setup.set_value[1])).
+//       For details cf.
+//       https://connect.microsoft.com/VisualStudio/feedback/details/209229/offsetof-does-not-produce-a-constant-expression-for-array-members
+//       Hopefully, this will be fixed sometime in the future.
+//       For lack of better alternatives, we currently hardcode the offsets when constant
+//       expressions are needed via PICA_REG_INDEX_WORKAROUND (on sane compilers, static_asserts
+//       will then make sure the offsets indeed match the automatically calculated ones).
+#define PICA_REG_INDEX(field_name) (offsetof(Pica::Regs, field_name) / sizeof(u32))
+#if defined(_MSC_VER)
+#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) (backup_workaround_index)
+#else
+// NOTE: Yeah, hacking in a static_assert here just to workaround the lacking MSVC compiler
+//       really is this annoying. This macro just forwards its first argument to PICA_REG_INDEX
+//       and then performs a (no-op) cast to size_t iff the second argument matches the expected
+//       field offset. Otherwise, the compiler will fail to compile this code.
+#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index)                             \
+    ((typename std::enable_if<backup_workaround_index == PICA_REG_INDEX(field_name),               \
+                              size_t>::type)PICA_REG_INDEX(field_name))
+#endif // _MSC_VER
+
+struct Regs {
+    INSERT_PADDING_WORDS(0x10);
+    u32 trigger_irq;
+    INSERT_PADDING_WORDS(0x2f);
+    RasterizerRegs rasterizer;
+    TexturingRegs texturing;
+    FramebufferRegs framebuffer;
+    LightingRegs lighting;
+    PipelineRegs pipeline;
+    ShaderRegs gs;
+    ShaderRegs vs;
+    INSERT_PADDING_WORDS(0x20);
+
+    // Map register indices to names readable by humans
+    // Used for debugging purposes, so performance is not an issue here
+    static std::string GetCommandName(int index);
+
+    static constexpr size_t NumIds() {
+        return sizeof(Regs) / sizeof(u32);
+    }
+
+    const u32& operator[](int index) const {
+        const u32* content = reinterpret_cast<const u32*>(this);
+        return content[index];
+    }
+
+    u32& operator[](int index) {
+        u32* content = reinterpret_cast<u32*>(this);
+        return content[index];
+    }
+
+private:
+    /*
+    * Most physical addresses which Pica registers refer to are 8-byte aligned.
+    * This function should be used to get the address from a raw register value.
+    */
+    static inline u32 DecodeAddressRegister(u32 register_value) {
+        return register_value * 8;
+    }
+};
+
+// TODO: MSVC does not support using offsetof() on non-static data members even though this
+//       is technically allowed since C++11. This macro should be enabled once MSVC adds
+//       support for that.
+#ifndef _MSC_VER
+#define ASSERT_REG_POSITION(field_name, position)                                                  \
+    static_assert(offsetof(Regs, field_name) == position * 4,                                      \
+                  "Field " #field_name " has invalid position")
+
+ASSERT_REG_POSITION(trigger_irq, 0x10);
+
+ASSERT_REG_POSITION(rasterizer, 0x40);
+ASSERT_REG_POSITION(rasterizer.cull_mode, 0x40);
+ASSERT_REG_POSITION(rasterizer.viewport_size_x, 0x41);
+ASSERT_REG_POSITION(rasterizer.viewport_size_y, 0x43);
+ASSERT_REG_POSITION(rasterizer.viewport_depth_range, 0x4d);
+ASSERT_REG_POSITION(rasterizer.viewport_depth_near_plane, 0x4e);
+ASSERT_REG_POSITION(rasterizer.vs_output_attributes[0], 0x50);
+ASSERT_REG_POSITION(rasterizer.vs_output_attributes[1], 0x51);
+ASSERT_REG_POSITION(rasterizer.scissor_test, 0x65);
+ASSERT_REG_POSITION(rasterizer.viewport_corner, 0x68);
+ASSERT_REG_POSITION(rasterizer.depthmap_enable, 0x6D);
+
+ASSERT_REG_POSITION(texturing, 0x80);
+ASSERT_REG_POSITION(texturing.texture0_enable, 0x80);
+ASSERT_REG_POSITION(texturing.texture0, 0x81);
+ASSERT_REG_POSITION(texturing.texture0_format, 0x8e);
+ASSERT_REG_POSITION(texturing.fragment_lighting_enable, 0x8f);
+ASSERT_REG_POSITION(texturing.texture1, 0x91);
+ASSERT_REG_POSITION(texturing.texture1_format, 0x96);
+ASSERT_REG_POSITION(texturing.texture2, 0x99);
+ASSERT_REG_POSITION(texturing.texture2_format, 0x9e);
+ASSERT_REG_POSITION(texturing.tev_stage0, 0xc0);
+ASSERT_REG_POSITION(texturing.tev_stage1, 0xc8);
+ASSERT_REG_POSITION(texturing.tev_stage2, 0xd0);
+ASSERT_REG_POSITION(texturing.tev_stage3, 0xd8);
+ASSERT_REG_POSITION(texturing.tev_combiner_buffer_input, 0xe0);
+ASSERT_REG_POSITION(texturing.fog_mode, 0xe0);
+ASSERT_REG_POSITION(texturing.fog_color, 0xe1);
+ASSERT_REG_POSITION(texturing.fog_lut_offset, 0xe6);
+ASSERT_REG_POSITION(texturing.fog_lut_data, 0xe8);
+ASSERT_REG_POSITION(texturing.tev_stage4, 0xf0);
+ASSERT_REG_POSITION(texturing.tev_stage5, 0xf8);
+ASSERT_REG_POSITION(texturing.tev_combiner_buffer_color, 0xfd);
+
+ASSERT_REG_POSITION(framebuffer, 0x100);
+ASSERT_REG_POSITION(framebuffer.output_merger, 0x100);
+ASSERT_REG_POSITION(framebuffer.framebuffer, 0x110);
+
+ASSERT_REG_POSITION(lighting, 0x140);
+
+ASSERT_REG_POSITION(pipeline, 0x200);
+ASSERT_REG_POSITION(pipeline.vertex_attributes, 0x200);
+ASSERT_REG_POSITION(pipeline.index_array, 0x227);
+ASSERT_REG_POSITION(pipeline.num_vertices, 0x228);
+ASSERT_REG_POSITION(pipeline.vertex_offset, 0x22a);
+ASSERT_REG_POSITION(pipeline.trigger_draw, 0x22e);
+ASSERT_REG_POSITION(pipeline.trigger_draw_indexed, 0x22f);
+ASSERT_REG_POSITION(pipeline.vs_default_attributes_setup, 0x232);
+ASSERT_REG_POSITION(pipeline.command_buffer, 0x238);
+ASSERT_REG_POSITION(pipeline.gpu_mode, 0x245);
+ASSERT_REG_POSITION(pipeline.triangle_topology, 0x25e);
+ASSERT_REG_POSITION(pipeline.restart_primitive, 0x25f);
+
+ASSERT_REG_POSITION(gs, 0x280);
+ASSERT_REG_POSITION(vs, 0x2b0);
+
+#undef ASSERT_REG_POSITION
+#endif // !defined(_MSC_VER)
+
+// The total number of registers is chosen arbitrarily, but let's make sure it's not some odd value
+// anyway.
+static_assert(sizeof(Regs) <= 0x300 * sizeof(u32),
+              "Register set structure larger than it should be");
+static_assert(sizeof(Regs) >= 0x300 * sizeof(u32),
+              "Register set structure smaller than it should be");
+
+} // namespace Pica
diff --git a/src/video_core/regs_framebuffer.h b/src/video_core/regs_framebuffer.h
new file mode 100644
index 0000000000..366782080a
--- /dev/null
+++ b/src/video_core/regs_framebuffer.h
@@ -0,0 +1,284 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+
+namespace Pica {
+
+struct FramebufferRegs {
+    enum class LogicOp : u32 {
+        Clear = 0,
+        And = 1,
+        AndReverse = 2,
+        Copy = 3,
+        Set = 4,
+        CopyInverted = 5,
+        NoOp = 6,
+        Invert = 7,
+        Nand = 8,
+        Or = 9,
+        Nor = 10,
+        Xor = 11,
+        Equiv = 12,
+        AndInverted = 13,
+        OrReverse = 14,
+        OrInverted = 15,
+    };
+
+    enum class BlendEquation : u32 {
+        Add = 0,
+        Subtract = 1,
+        ReverseSubtract = 2,
+        Min = 3,
+        Max = 4,
+    };
+
+    enum class BlendFactor : u32 {
+        Zero = 0,
+        One = 1,
+        SourceColor = 2,
+        OneMinusSourceColor = 3,
+        DestColor = 4,
+        OneMinusDestColor = 5,
+        SourceAlpha = 6,
+        OneMinusSourceAlpha = 7,
+        DestAlpha = 8,
+        OneMinusDestAlpha = 9,
+        ConstantColor = 10,
+        OneMinusConstantColor = 11,
+        ConstantAlpha = 12,
+        OneMinusConstantAlpha = 13,
+        SourceAlphaSaturate = 14,
+    };
+
+    enum class CompareFunc : u32 {
+        Never = 0,
+        Always = 1,
+        Equal = 2,
+        NotEqual = 3,
+        LessThan = 4,
+        LessThanOrEqual = 5,
+        GreaterThan = 6,
+        GreaterThanOrEqual = 7,
+    };
+
+    enum class StencilAction : u32 {
+        Keep = 0,
+        Zero = 1,
+        Replace = 2,
+        Increment = 3,
+        Decrement = 4,
+        Invert = 5,
+        IncrementWrap = 6,
+        DecrementWrap = 7,
+    };
+
+    struct {
+        union {
+            // If false, logic blending is used
+            BitField<8, 1, u32> alphablend_enable;
+        };
+
+        union {
+            BitField<0, 8, BlendEquation> blend_equation_rgb;
+            BitField<8, 8, BlendEquation> blend_equation_a;
+
+            BitField<16, 4, BlendFactor> factor_source_rgb;
+            BitField<20, 4, BlendFactor> factor_dest_rgb;
+
+            BitField<24, 4, BlendFactor> factor_source_a;
+            BitField<28, 4, BlendFactor> factor_dest_a;
+        } alpha_blending;
+
+        union {
+            BitField<0, 4, LogicOp> logic_op;
+        };
+
+        union {
+            u32 raw;
+            BitField<0, 8, u32> r;
+            BitField<8, 8, u32> g;
+            BitField<16, 8, u32> b;
+            BitField<24, 8, u32> a;
+        } blend_const;
+
+        union {
+            BitField<0, 1, u32> enable;
+            BitField<4, 3, CompareFunc> func;
+            BitField<8, 8, u32> ref;
+        } alpha_test;
+
+        struct {
+            union {
+                // Raw value of this register
+                u32 raw_func;
+
+                // If true, enable stencil testing
+                BitField<0, 1, u32> enable;
+
+                // Comparison operation for stencil testing
+                BitField<4, 3, CompareFunc> func;
+
+                // Mask used to control writing to the stencil buffer
+                BitField<8, 8, u32> write_mask;
+
+                // Value to compare against for stencil testing
+                BitField<16, 8, u32> reference_value;
+
+                // Mask to apply on stencil test inputs
+                BitField<24, 8, u32> input_mask;
+            };
+
+            union {
+                // Raw value of this register
+                u32 raw_op;
+
+                // Action to perform when the stencil test fails
+                BitField<0, 3, StencilAction> action_stencil_fail;
+
+                // Action to perform when stencil testing passed but depth testing fails
+                BitField<4, 3, StencilAction> action_depth_fail;
+
+                // Action to perform when both stencil and depth testing pass
+                BitField<8, 3, StencilAction> action_depth_pass;
+            };
+        } stencil_test;
+
+        union {
+            BitField<0, 1, u32> depth_test_enable;
+            BitField<4, 3, CompareFunc> depth_test_func;
+            BitField<8, 1, u32> red_enable;
+            BitField<9, 1, u32> green_enable;
+            BitField<10, 1, u32> blue_enable;
+            BitField<11, 1, u32> alpha_enable;
+            BitField<12, 1, u32> depth_write_enable;
+        };
+
+        INSERT_PADDING_WORDS(0x8);
+    } output_merger;
+
+    // Components are laid out in reverse byte order, most significant bits first.
+    enum class ColorFormat : u32 {
+        RGBA8 = 0,
+        RGB8 = 1,
+        RGB5A1 = 2,
+        RGB565 = 3,
+        RGBA4 = 4,
+    };
+
+    enum class DepthFormat : u32 {
+        D16 = 0,
+        D24 = 2,
+        D24S8 = 3,
+    };
+
+    // Returns the number of bytes in the specified color format
+    static unsigned BytesPerColorPixel(ColorFormat format) {
+        switch (format) {
+        case ColorFormat::RGBA8:
+            return 4;
+        case ColorFormat::RGB8:
+            return 3;
+        case ColorFormat::RGB5A1:
+        case ColorFormat::RGB565:
+        case ColorFormat::RGBA4:
+            return 2;
+        default:
+            LOG_CRITICAL(HW_GPU, "Unknown color format %u", format);
+            UNIMPLEMENTED();
+        }
+    }
+
+    struct FramebufferConfig {
+        INSERT_PADDING_WORDS(0x3);
+
+        union {
+            BitField<0, 4, u32> allow_color_write; // 0 = disable, else enable
+        };
+
+        INSERT_PADDING_WORDS(0x1);
+
+        union {
+            BitField<0, 2, u32> allow_depth_stencil_write; // 0 = disable, else enable
+        };
+
+        DepthFormat depth_format; // TODO: Should be a BitField!
+        BitField<16, 3, ColorFormat> color_format;
+
+        INSERT_PADDING_WORDS(0x4);
+
+        u32 depth_buffer_address;
+        u32 color_buffer_address;
+
+        union {
+            // Apparently, the framebuffer width is stored as expected,
+            // while the height is stored as the actual height minus one.
+            // Hence, don't access these fields directly but use the accessors
+            // GetWidth() and GetHeight() instead.
+            BitField<0, 11, u32> width;
+            BitField<12, 10, u32> height;
+        };
+
+        INSERT_PADDING_WORDS(0x1);
+
+        inline PAddr GetColorBufferPhysicalAddress() const {
+            return color_buffer_address * 8;
+        }
+        inline PAddr GetDepthBufferPhysicalAddress() const {
+            return depth_buffer_address * 8;
+        }
+
+        inline u32 GetWidth() const {
+            return width;
+        }
+
+        inline u32 GetHeight() const {
+            return height + 1;
+        }
+    } framebuffer;
+
+    // Returns the number of bytes in the specified depth format
+    static u32 BytesPerDepthPixel(DepthFormat format) {
+        switch (format) {
+        case DepthFormat::D16:
+            return 2;
+        case DepthFormat::D24:
+            return 3;
+        case DepthFormat::D24S8:
+            return 4;
+        default:
+            LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format);
+            UNIMPLEMENTED();
+        }
+    }
+
+    // Returns the number of bits per depth component of the specified depth format
+    static u32 DepthBitsPerPixel(DepthFormat format) {
+        switch (format) {
+        case DepthFormat::D16:
+            return 16;
+        case DepthFormat::D24:
+        case DepthFormat::D24S8:
+            return 24;
+        default:
+            LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format);
+            UNIMPLEMENTED();
+        }
+    }
+
+    INSERT_PADDING_WORDS(0x20);
+};
+
+static_assert(sizeof(FramebufferRegs) == 0x40 * sizeof(u32),
+              "FramebufferRegs struct has incorrect size");
+
+} // namespace Pica
diff --git a/src/video_core/regs_lighting.h b/src/video_core/regs_lighting.h
new file mode 100644
index 0000000000..548a6c4d50
--- /dev/null
+++ b/src/video_core/regs_lighting.h
@@ -0,0 +1,294 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/vector_math.h"
+
+namespace Pica {
+
+struct LightingRegs {
+    enum class LightingSampler {
+        Distribution0 = 0,
+        Distribution1 = 1,
+        Fresnel = 3,
+        ReflectBlue = 4,
+        ReflectGreen = 5,
+        ReflectRed = 6,
+        SpotlightAttenuation = 8,
+        DistanceAttenuation = 16,
+    };
+
+    /**
+    * Pica fragment lighting supports using different LUTs for each lighting component:  Reflectance
+    * R, G, and B channels, distribution function for specular components 0 and 1, fresnel factor,
+    * and spotlight attenuation.  Furthermore, which LUTs are used for each channel (or whether a
+    * channel is enabled at all) is specified by various pre-defined lighting configurations.  With
+    * configurations that require more LUTs, more cycles are required on HW to perform lighting
+    * computations.
+    */
+    enum class LightingConfig {
+        Config0 = 0, ///< Reflect Red, Distribution 0, Spotlight
+        Config1 = 1, ///< Reflect Red, Fresnel, Spotlight
+        Config2 = 2, ///< Reflect Red, Distribution 0/1
+        Config3 = 3, ///< Distribution 0/1, Fresnel
+        Config4 = 4, ///< Reflect Red/Green/Blue, Distribution 0/1, Spotlight
+        Config5 = 5, ///< Reflect Red/Green/Blue, Distribution 0, Fresnel, Spotlight
+        Config6 = 6, ///< Reflect Red, Distribution 0/1, Fresnel, Spotlight
+
+        Config7 = 8, ///< Reflect Red/Green/Blue, Distribution 0/1, Fresnel, Spotlight
+                     ///< NOTE: '8' is intentional, '7' does not appear to be a valid configuration
+    };
+
+    /// Selects which lighting components are affected by fresnel
+    enum class LightingFresnelSelector {
+        None = 0,           ///< Fresnel is disabled
+        PrimaryAlpha = 1,   ///< Primary (diffuse) lighting alpha is affected by fresnel
+        SecondaryAlpha = 2, ///< Secondary (specular) lighting alpha is affected by fresnel
+        Both =
+            PrimaryAlpha |
+            SecondaryAlpha, ///< Both primary and secondary lighting alphas are affected by fresnel
+    };
+
+    /// Factor used to scale the output of a lighting LUT
+    enum class LightingScale {
+        Scale1 = 0, ///< Scale is 1x
+        Scale2 = 1, ///< Scale is 2x
+        Scale4 = 2, ///< Scale is 4x
+        Scale8 = 3, ///< Scale is 8x
+
+        Scale1_4 = 6, ///< Scale is 0.25x
+        Scale1_2 = 7, ///< Scale is 0.5x
+    };
+
+    enum class LightingLutInput {
+        NH = 0, // Cosine of the angle between the normal and half-angle vectors
+        VH = 1, // Cosine of the angle between the view and half-angle vectors
+        NV = 2, // Cosine of the angle between the normal and the view vector
+        LN = 3, // Cosine of the angle between the light and the normal vectors
+    };
+
+    enum class LightingBumpMode : u32 {
+        None = 0,
+        NormalMap = 1,
+        TangentMap = 2,
+    };
+
+    union LightColor {
+        BitField<0, 10, u32> b;
+        BitField<10, 10, u32> g;
+        BitField<20, 10, u32> r;
+
+        Math::Vec3f ToVec3f() const {
+            // These fields are 10 bits wide, however 255 corresponds to 1.0f for each color
+            // component
+            return Math::MakeVec((f32)r / 255.f, (f32)g / 255.f, (f32)b / 255.f);
+        }
+    };
+
+    /// Returns true if the specified lighting sampler is supported by the current Pica lighting
+    /// configuration
+    static bool IsLightingSamplerSupported(LightingConfig config, LightingSampler sampler) {
+        switch (sampler) {
+        case LightingSampler::Distribution0:
+            return (config != LightingConfig::Config1);
+
+        case LightingSampler::Distribution1:
+            return (config != LightingConfig::Config0) && (config != LightingConfig::Config1) &&
+                   (config != LightingConfig::Config5);
+
+        case LightingSampler::Fresnel:
+            return (config != LightingConfig::Config0) && (config != LightingConfig::Config2) &&
+                   (config != LightingConfig::Config4);
+
+        case LightingSampler::ReflectRed:
+            return (config != LightingConfig::Config3);
+
+        case LightingSampler::ReflectGreen:
+        case LightingSampler::ReflectBlue:
+            return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) ||
+                   (config == LightingConfig::Config7);
+        default:
+            UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached "
+                            "unreachable section, sampler should be one "
+                            "of Distribution0, Distribution1, Fresnel, "
+                            "ReflectRed, ReflectGreen or ReflectBlue, instead "
+                            "got %i",
+                            static_cast<int>(config));
+        }
+    }
+
+    struct LightSrc {
+        LightColor specular_0; // material.specular_0 * light.specular_0
+        LightColor specular_1; // material.specular_1 * light.specular_1
+        LightColor diffuse;    // material.diffuse * light.diffuse
+        LightColor ambient;    // material.ambient * light.ambient
+
+        // Encoded as 16-bit floating point
+        union {
+            BitField<0, 16, u32> x;
+            BitField<16, 16, u32> y;
+        };
+        union {
+            BitField<0, 16, u32> z;
+        };
+
+        INSERT_PADDING_WORDS(0x3);
+
+        union {
+            BitField<0, 1, u32> directional;
+            BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0
+        } config;
+
+        BitField<0, 20, u32> dist_atten_bias;
+        BitField<0, 20, u32> dist_atten_scale;
+
+        INSERT_PADDING_WORDS(0x4);
+    };
+    static_assert(sizeof(LightSrc) == 0x10 * sizeof(u32), "LightSrc structure must be 0x10 words");
+
+    LightSrc light[8];
+    LightColor global_ambient; // Emission + (material.ambient * lighting.ambient)
+    INSERT_PADDING_WORDS(0x1);
+    BitField<0, 3, u32> max_light_index; // Number of enabled lights - 1
+
+    union {
+        BitField<2, 2, LightingFresnelSelector> fresnel_selector;
+        BitField<4, 4, LightingConfig> config;
+        BitField<22, 2, u32> bump_selector; // 0: Texture 0, 1: Texture 1, 2: Texture 2
+        BitField<27, 1, u32> clamp_highlights;
+        BitField<28, 2, LightingBumpMode> bump_mode;
+        BitField<30, 1, u32> disable_bump_renorm;
+    } config0;
+
+    union {
+        BitField<16, 1, u32> disable_lut_d0;
+        BitField<17, 1, u32> disable_lut_d1;
+        BitField<19, 1, u32> disable_lut_fr;
+        BitField<20, 1, u32> disable_lut_rr;
+        BitField<21, 1, u32> disable_lut_rg;
+        BitField<22, 1, u32> disable_lut_rb;
+
+        // Each bit specifies whether distance attenuation should be applied for the corresponding
+        // light.
+        BitField<24, 1, u32> disable_dist_atten_light_0;
+        BitField<25, 1, u32> disable_dist_atten_light_1;
+        BitField<26, 1, u32> disable_dist_atten_light_2;
+        BitField<27, 1, u32> disable_dist_atten_light_3;
+        BitField<28, 1, u32> disable_dist_atten_light_4;
+        BitField<29, 1, u32> disable_dist_atten_light_5;
+        BitField<30, 1, u32> disable_dist_atten_light_6;
+        BitField<31, 1, u32> disable_dist_atten_light_7;
+    } config1;
+
+    bool IsDistAttenDisabled(unsigned index) const {
+        const unsigned disable[] = {
+            config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1,
+            config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3,
+            config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5,
+            config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7};
+        return disable[index] != 0;
+    }
+
+    union {
+        BitField<0, 8, u32> index; ///< Index at which to set data in the LUT
+        BitField<8, 5, u32> type;  ///< Type of LUT for which to set data
+    } lut_config;
+
+    BitField<0, 1, u32> disable;
+    INSERT_PADDING_WORDS(0x1);
+
+    // When data is written to any of these registers, it gets written to the lookup table of the
+    // selected type at the selected index, specified above in the `lut_config` register.  With each
+    // write, `lut_config.index` is incremented.  It does not matter which of these registers is
+    // written to, the behavior will be the same.
+    u32 lut_data[8];
+
+    // These are used to specify if absolute (abs) value should be used for each LUT index.  When
+    // abs mode is disabled, LUT indexes are in the range of (-1.0, 1.0).  Otherwise, they are in
+    // the range of (0.0, 1.0).
+    union {
+        BitField<1, 1, u32> disable_d0;
+        BitField<5, 1, u32> disable_d1;
+        BitField<9, 1, u32> disable_sp;
+        BitField<13, 1, u32> disable_fr;
+        BitField<17, 1, u32> disable_rb;
+        BitField<21, 1, u32> disable_rg;
+        BitField<25, 1, u32> disable_rr;
+    } abs_lut_input;
+
+    union {
+        BitField<0, 3, LightingLutInput> d0;
+        BitField<4, 3, LightingLutInput> d1;
+        BitField<8, 3, LightingLutInput> sp;
+        BitField<12, 3, LightingLutInput> fr;
+        BitField<16, 3, LightingLutInput> rb;
+        BitField<20, 3, LightingLutInput> rg;
+        BitField<24, 3, LightingLutInput> rr;
+    } lut_input;
+
+    union {
+        BitField<0, 3, LightingScale> d0;
+        BitField<4, 3, LightingScale> d1;
+        BitField<8, 3, LightingScale> sp;
+        BitField<12, 3, LightingScale> fr;
+        BitField<16, 3, LightingScale> rb;
+        BitField<20, 3, LightingScale> rg;
+        BitField<24, 3, LightingScale> rr;
+
+        static float GetScale(LightingScale scale) {
+            switch (scale) {
+            case LightingScale::Scale1:
+                return 1.0f;
+            case LightingScale::Scale2:
+                return 2.0f;
+            case LightingScale::Scale4:
+                return 4.0f;
+            case LightingScale::Scale8:
+                return 8.0f;
+            case LightingScale::Scale1_4:
+                return 0.25f;
+            case LightingScale::Scale1_2:
+                return 0.5f;
+            }
+            return 0.0f;
+        }
+    } lut_scale;
+
+    INSERT_PADDING_WORDS(0x6);
+
+    union {
+        // There are 8 light enable "slots", corresponding to the total number of lights supported
+        // by Pica.  For N enabled lights (specified by register 0x1c2, or 'src_num' above), the
+        // first N slots below will be set to integers within the range of 0-7, corresponding to the
+        // actual light that is enabled for each slot.
+
+        BitField<0, 3, u32> slot_0;
+        BitField<4, 3, u32> slot_1;
+        BitField<8, 3, u32> slot_2;
+        BitField<12, 3, u32> slot_3;
+        BitField<16, 3, u32> slot_4;
+        BitField<20, 3, u32> slot_5;
+        BitField<24, 3, u32> slot_6;
+        BitField<28, 3, u32> slot_7;
+
+        unsigned GetNum(unsigned index) const {
+            const unsigned enable_slots[] = {slot_0, slot_1, slot_2, slot_3,
+                                             slot_4, slot_5, slot_6, slot_7};
+            return enable_slots[index];
+        }
+    } light_enable;
+
+    INSERT_PADDING_WORDS(0x26);
+};
+
+static_assert(sizeof(LightingRegs) == 0xC0 * sizeof(u32), "LightingRegs struct has incorrect size");
+
+} // namespace Pica
diff --git a/src/video_core/regs_pipeline.h b/src/video_core/regs_pipeline.h
new file mode 100644
index 0000000000..5844a66eef
--- /dev/null
+++ b/src/video_core/regs_pipeline.h
@@ -0,0 +1,224 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Pica {
+
+struct PipelineRegs {
+    enum class VertexAttributeFormat : u64 {
+        BYTE = 0,
+        UBYTE = 1,
+        SHORT = 2,
+        FLOAT = 3,
+    };
+
+    struct {
+        BitField<0, 29, u32> base_address;
+
+        PAddr GetPhysicalBaseAddress() const {
+            return base_address * 8;
+        }
+
+        // Descriptor for internal vertex attributes
+        union {
+            BitField<0, 2, VertexAttributeFormat> format0; // size of one element
+            BitField<2, 2, u64> size0;                     // number of elements minus 1
+            BitField<4, 2, VertexAttributeFormat> format1;
+            BitField<6, 2, u64> size1;
+            BitField<8, 2, VertexAttributeFormat> format2;
+            BitField<10, 2, u64> size2;
+            BitField<12, 2, VertexAttributeFormat> format3;
+            BitField<14, 2, u64> size3;
+            BitField<16, 2, VertexAttributeFormat> format4;
+            BitField<18, 2, u64> size4;
+            BitField<20, 2, VertexAttributeFormat> format5;
+            BitField<22, 2, u64> size5;
+            BitField<24, 2, VertexAttributeFormat> format6;
+            BitField<26, 2, u64> size6;
+            BitField<28, 2, VertexAttributeFormat> format7;
+            BitField<30, 2, u64> size7;
+            BitField<32, 2, VertexAttributeFormat> format8;
+            BitField<34, 2, u64> size8;
+            BitField<36, 2, VertexAttributeFormat> format9;
+            BitField<38, 2, u64> size9;
+            BitField<40, 2, VertexAttributeFormat> format10;
+            BitField<42, 2, u64> size10;
+            BitField<44, 2, VertexAttributeFormat> format11;
+            BitField<46, 2, u64> size11;
+
+            BitField<48, 12, u64> attribute_mask;
+
+            // number of total attributes minus 1
+            BitField<60, 4, u64> max_attribute_index;
+        };
+
+        inline VertexAttributeFormat GetFormat(int n) const {
+            VertexAttributeFormat formats[] = {format0, format1, format2,  format3,
+                                               format4, format5, format6,  format7,
+                                               format8, format9, format10, format11};
+            return formats[n];
+        }
+
+        inline int GetNumElements(int n) const {
+            u64 sizes[] = {size0, size1, size2, size3, size4,  size5,
+                           size6, size7, size8, size9, size10, size11};
+            return (int)sizes[n] + 1;
+        }
+
+        inline int GetElementSizeInBytes(int n) const {
+            return (GetFormat(n) == VertexAttributeFormat::FLOAT)
+                       ? 4
+                       : (GetFormat(n) == VertexAttributeFormat::SHORT) ? 2 : 1;
+        }
+
+        inline int GetStride(int n) const {
+            return GetNumElements(n) * GetElementSizeInBytes(n);
+        }
+
+        inline bool IsDefaultAttribute(int id) const {
+            return (id >= 12) || (attribute_mask & (1ULL << id)) != 0;
+        }
+
+        inline int GetNumTotalAttributes() const {
+            return (int)max_attribute_index + 1;
+        }
+
+        // Attribute loaders map the source vertex data to input attributes
+        // This e.g. allows to load different attributes from different memory locations
+        struct {
+            // Source attribute data offset from the base address
+            u32 data_offset;
+
+            union {
+                BitField<0, 4, u64> comp0;
+                BitField<4, 4, u64> comp1;
+                BitField<8, 4, u64> comp2;
+                BitField<12, 4, u64> comp3;
+                BitField<16, 4, u64> comp4;
+                BitField<20, 4, u64> comp5;
+                BitField<24, 4, u64> comp6;
+                BitField<28, 4, u64> comp7;
+                BitField<32, 4, u64> comp8;
+                BitField<36, 4, u64> comp9;
+                BitField<40, 4, u64> comp10;
+                BitField<44, 4, u64> comp11;
+
+                // bytes for a single vertex in this loader
+                BitField<48, 8, u64> byte_count;
+
+                BitField<60, 4, u64> component_count;
+            };
+
+            inline int GetComponent(int n) const {
+                u64 components[] = {comp0, comp1, comp2, comp3, comp4,  comp5,
+                                    comp6, comp7, comp8, comp9, comp10, comp11};
+                return (int)components[n];
+            }
+        } attribute_loaders[12];
+    } vertex_attributes;
+
+    struct {
+        enum IndexFormat : u32 {
+            BYTE = 0,
+            SHORT = 1,
+        };
+
+        union {
+            BitField<0, 31, u32> offset; // relative to base attribute address
+            BitField<31, 1, IndexFormat> format;
+        };
+    } index_array;
+
+    // Number of vertices to render
+    u32 num_vertices;
+
+    INSERT_PADDING_WORDS(0x1);
+
+    // The index of the first vertex to render
+    u32 vertex_offset;
+
+    INSERT_PADDING_WORDS(0x3);
+
+    // These two trigger rendering of triangles
+    u32 trigger_draw;
+    u32 trigger_draw_indexed;
+
+    INSERT_PADDING_WORDS(0x2);
+
+    // These registers are used to setup the default "fall-back" vertex shader attributes
+    struct {
+        // Index of the current default attribute
+        u32 index;
+
+        // Writing to these registers sets the "current" default attribute.
+        u32 set_value[3];
+    } vs_default_attributes_setup;
+
+    INSERT_PADDING_WORDS(0x2);
+
+    struct {
+        // There are two channels that can be used to configure the next command buffer, which can
+        // be then executed by writing to the "trigger" registers. There are two reasons why a game
+        // might use this feature:
+        //  1) With this, an arbitrary number of additional command buffers may be executed in
+        //     sequence without requiring any intervention of the CPU after the initial one is
+        //     kicked off.
+        //  2) Games can configure these registers to provide a command list subroutine mechanism.
+
+        BitField<0, 20, u32> size[2]; ///< Size (in bytes / 8) of each channel's command buffer
+        BitField<0, 28, u32> addr[2]; ///< Physical address / 8 of each channel's command buffer
+        u32 trigger[2]; ///< Triggers execution of the channel's command buffer when written to
+
+        unsigned GetSize(unsigned index) const {
+            ASSERT(index < 2);
+            return 8 * size[index];
+        }
+
+        PAddr GetPhysicalAddress(unsigned index) const {
+            ASSERT(index < 2);
+            return (PAddr)(8 * addr[index]);
+        }
+    } command_buffer;
+
+    INSERT_PADDING_WORDS(4);
+
+    /// Number of input attributes to the vertex shader minus 1
+    BitField<0, 4, u32> max_input_attrib_index;
+
+    INSERT_PADDING_WORDS(2);
+
+    enum class GPUMode : u32 {
+        Drawing = 0,
+        Configuring = 1,
+    };
+
+    GPUMode gpu_mode;
+
+    INSERT_PADDING_WORDS(0x18);
+
+    enum class TriangleTopology : u32 {
+        List = 0,
+        Strip = 1,
+        Fan = 2,
+        Shader = 3, // Programmable setup unit implemented in a geometry shader
+    };
+
+    BitField<8, 2, TriangleTopology> triangle_topology;
+
+    u32 restart_primitive;
+
+    INSERT_PADDING_WORDS(0x20);
+};
+
+static_assert(sizeof(PipelineRegs) == 0x80 * sizeof(u32), "PipelineRegs struct has incorrect size");
+
+} // namespace Pica
diff --git a/src/video_core/regs_rasterizer.h b/src/video_core/regs_rasterizer.h
new file mode 100644
index 0000000000..a471a3b386
--- /dev/null
+++ b/src/video_core/regs_rasterizer.h
@@ -0,0 +1,129 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Pica {
+
+struct RasterizerRegs {
+    enum class CullMode : u32 {
+        // Select which polygons are considered to be "frontfacing".
+        KeepAll = 0,
+        KeepClockWise = 1,
+        KeepCounterClockWise = 2,
+        // TODO: What does the third value imply?
+    };
+
+    union {
+        BitField<0, 2, CullMode> cull_mode;
+    };
+
+    BitField<0, 24, u32> viewport_size_x;
+
+    INSERT_PADDING_WORDS(0x1);
+
+    BitField<0, 24, u32> viewport_size_y;
+
+    INSERT_PADDING_WORDS(0x9);
+
+    BitField<0, 24, u32> viewport_depth_range;      // float24
+    BitField<0, 24, u32> viewport_depth_near_plane; // float24
+
+    BitField<0, 3, u32> vs_output_total;
+
+    union VSOutputAttributes {
+        // Maps components of output vertex attributes to semantics
+        enum Semantic : u32 {
+            POSITION_X = 0,
+            POSITION_Y = 1,
+            POSITION_Z = 2,
+            POSITION_W = 3,
+
+            QUATERNION_X = 4,
+            QUATERNION_Y = 5,
+            QUATERNION_Z = 6,
+            QUATERNION_W = 7,
+
+            COLOR_R = 8,
+            COLOR_G = 9,
+            COLOR_B = 10,
+            COLOR_A = 11,
+
+            TEXCOORD0_U = 12,
+            TEXCOORD0_V = 13,
+            TEXCOORD1_U = 14,
+            TEXCOORD1_V = 15,
+
+            TEXCOORD0_W = 16,
+
+            VIEW_X = 18,
+            VIEW_Y = 19,
+            VIEW_Z = 20,
+
+            TEXCOORD2_U = 22,
+            TEXCOORD2_V = 23,
+
+            INVALID = 31,
+        };
+
+        BitField<0, 5, Semantic> map_x;
+        BitField<8, 5, Semantic> map_y;
+        BitField<16, 5, Semantic> map_z;
+        BitField<24, 5, Semantic> map_w;
+    } vs_output_attributes[7];
+
+    INSERT_PADDING_WORDS(0xe);
+
+    enum class ScissorMode : u32 {
+        Disabled = 0,
+        Exclude = 1, // Exclude pixels inside the scissor box
+
+        Include = 3 // Exclude pixels outside the scissor box
+    };
+
+    struct {
+        BitField<0, 2, ScissorMode> mode;
+
+        union {
+            BitField<0, 16, u32> x1;
+            BitField<16, 16, u32> y1;
+        };
+
+        union {
+            BitField<0, 16, u32> x2;
+            BitField<16, 16, u32> y2;
+        };
+    } scissor_test;
+
+    union {
+        BitField<0, 10, s32> x;
+        BitField<16, 10, s32> y;
+    } viewport_corner;
+
+    INSERT_PADDING_WORDS(0x1);
+
+    // TODO: early depth
+    INSERT_PADDING_WORDS(0x1);
+
+    INSERT_PADDING_WORDS(0x2);
+
+    enum DepthBuffering : u32 {
+        WBuffering = 0,
+        ZBuffering = 1,
+    };
+    BitField<0, 1, DepthBuffering> depthmap_enable;
+
+    INSERT_PADDING_WORDS(0x12);
+};
+
+static_assert(sizeof(RasterizerRegs) == 0x40 * sizeof(u32),
+              "RasterizerRegs struct has incorrect size");
+
+} // namespace Pica
diff --git a/src/video_core/regs_shader.h b/src/video_core/regs_shader.h
new file mode 100644
index 0000000000..ddb1ee4515
--- /dev/null
+++ b/src/video_core/regs_shader.h
@@ -0,0 +1,104 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Pica {
+
+struct ShaderRegs {
+    BitField<0, 16, u32> bool_uniforms;
+
+    union {
+        BitField<0, 8, u32> x;
+        BitField<8, 8, u32> y;
+        BitField<16, 8, u32> z;
+        BitField<24, 8, u32> w;
+    } int_uniforms[4];
+
+    INSERT_PADDING_WORDS(0x4);
+
+    union {
+        // Number of input attributes to shader unit - 1
+        BitField<0, 4, u32> max_input_attribute_index;
+    };
+
+    // Offset to shader program entry point (in words)
+    BitField<0, 16, u32> main_offset;
+
+    /// Maps input attributes to registers. 4-bits per attribute, specifying a register index
+    u32 input_attribute_to_register_map_low;
+    u32 input_attribute_to_register_map_high;
+
+    unsigned int GetRegisterForAttribute(unsigned int attribute_index) const {
+        u64 map = ((u64)input_attribute_to_register_map_high << 32) |
+                  (u64)input_attribute_to_register_map_low;
+        return (map >> (attribute_index * 4)) & 0b1111;
+    }
+
+    BitField<0, 16, u32> output_mask;
+
+    // 0x28E, CODETRANSFER_END
+    INSERT_PADDING_WORDS(0x2);
+
+    struct {
+        enum Format : u32 {
+            FLOAT24 = 0,
+            FLOAT32 = 1,
+        };
+
+        bool IsFloat32() const {
+            return format == FLOAT32;
+        }
+
+        union {
+            // Index of the next uniform to write to
+            // TODO: ctrulib uses 8 bits for this, however that seems to yield lots of invalid
+            // indices
+            // TODO: Maybe the uppermost index is for the geometry shader? Investigate!
+            BitField<0, 7, u32> index;
+
+            BitField<31, 1, Format> format;
+        };
+
+        // Writing to these registers sets the current uniform.
+        u32 set_value[8];
+
+    } uniform_setup;
+
+    INSERT_PADDING_WORDS(0x2);
+
+    struct {
+        // Offset of the next instruction to write code to.
+        // Incremented with each instruction write.
+        u32 offset;
+
+        // Writing to these registers sets the "current" word in the shader program.
+        u32 set_word[8];
+    } program;
+
+    INSERT_PADDING_WORDS(0x1);
+
+    // This register group is used to load an internal table of swizzling patterns,
+    // which are indexed by each shader instruction to specify vector component swizzling.
+    struct {
+        // Offset of the next swizzle pattern to write code to.
+        // Incremented with each instruction write.
+        u32 offset;
+
+        // Writing to these registers sets the current swizzle pattern in the table.
+        u32 set_word[8];
+    } swizzle_patterns;
+
+    INSERT_PADDING_WORDS(0x2);
+};
+
+static_assert(sizeof(ShaderRegs) == 0x30 * sizeof(u32), "ShaderRegs struct has incorrect size");
+
+} // namespace Pica
diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h
new file mode 100644
index 0000000000..be8bc68266
--- /dev/null
+++ b/src/video_core/regs_texturing.h
@@ -0,0 +1,328 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Pica {
+
+struct TexturingRegs {
+    struct TextureConfig {
+        enum TextureType : u32 {
+            Texture2D = 0,
+            TextureCube = 1,
+            Shadow2D = 2,
+            Projection2D = 3,
+            ShadowCube = 4,
+            Disabled = 5,
+        };
+
+        enum WrapMode : u32 {
+            ClampToEdge = 0,
+            ClampToBorder = 1,
+            Repeat = 2,
+            MirroredRepeat = 3,
+        };
+
+        enum TextureFilter : u32 {
+            Nearest = 0,
+            Linear = 1,
+        };
+
+        union {
+            u32 raw;
+            BitField<0, 8, u32> r;
+            BitField<8, 8, u32> g;
+            BitField<16, 8, u32> b;
+            BitField<24, 8, u32> a;
+        } border_color;
+
+        union {
+            BitField<0, 16, u32> height;
+            BitField<16, 16, u32> width;
+        };
+
+        union {
+            BitField<1, 1, TextureFilter> mag_filter;
+            BitField<2, 1, TextureFilter> min_filter;
+            BitField<8, 2, WrapMode> wrap_t;
+            BitField<12, 2, WrapMode> wrap_s;
+            BitField<28, 2, TextureType>
+                type; ///< @note Only valid for texture 0 according to 3DBrew.
+        };
+
+        INSERT_PADDING_WORDS(0x1);
+
+        u32 address;
+
+        PAddr GetPhysicalAddress() const {
+            return address * 8;
+        }
+
+        // texture1 and texture2 store the texture format directly after the address
+        // whereas texture0 inserts some additional flags inbetween.
+        // Hence, we store the format separately so that all other parameters can be described
+        // in a single structure.
+    };
+
+    enum class TextureFormat : u32 {
+        RGBA8 = 0,
+        RGB8 = 1,
+        RGB5A1 = 2,
+        RGB565 = 3,
+        RGBA4 = 4,
+        IA8 = 5,
+        RG8 = 6, ///< @note Also called HILO8 in 3DBrew.
+        I8 = 7,
+        A8 = 8,
+        IA4 = 9,
+        I4 = 10,
+        A4 = 11,
+        ETC1 = 12,   // compressed
+        ETC1A4 = 13, // compressed
+    };
+
+    static unsigned NibblesPerPixel(TextureFormat format) {
+        switch (format) {
+        case TextureFormat::RGBA8:
+            return 8;
+
+        case TextureFormat::RGB8:
+            return 6;
+
+        case TextureFormat::RGB5A1:
+        case TextureFormat::RGB565:
+        case TextureFormat::RGBA4:
+        case TextureFormat::IA8:
+        case TextureFormat::RG8:
+            return 4;
+
+        case TextureFormat::I4:
+        case TextureFormat::A4:
+            return 1;
+
+        case TextureFormat::I8:
+        case TextureFormat::A8:
+        case TextureFormat::IA4:
+
+        default: // placeholder for yet unknown formats
+            UNIMPLEMENTED();
+            return 0;
+        }
+    }
+
+    union {
+        BitField<0, 1, u32> texture0_enable;
+        BitField<1, 1, u32> texture1_enable;
+        BitField<2, 1, u32> texture2_enable;
+    };
+    TextureConfig texture0;
+    INSERT_PADDING_WORDS(0x8);
+    BitField<0, 4, TextureFormat> texture0_format;
+    BitField<0, 1, u32> fragment_lighting_enable;
+    INSERT_PADDING_WORDS(0x1);
+    TextureConfig texture1;
+    BitField<0, 4, TextureFormat> texture1_format;
+    INSERT_PADDING_WORDS(0x2);
+    TextureConfig texture2;
+    BitField<0, 4, TextureFormat> texture2_format;
+    INSERT_PADDING_WORDS(0x21);
+
+    struct FullTextureConfig {
+        const bool enabled;
+        const TextureConfig config;
+        const TextureFormat format;
+    };
+    const std::array<FullTextureConfig, 3> GetTextures() const {
+        return {{
+            {texture0_enable.ToBool(), texture0, texture0_format},
+            {texture1_enable.ToBool(), texture1, texture1_format},
+            {texture2_enable.ToBool(), texture2, texture2_format},
+        }};
+    }
+
+    // 0xc0-0xff: Texture Combiner (akin to glTexEnv)
+    struct TevStageConfig {
+        enum class Source : u32 {
+            PrimaryColor = 0x0,
+            PrimaryFragmentColor = 0x1,
+            SecondaryFragmentColor = 0x2,
+
+            Texture0 = 0x3,
+            Texture1 = 0x4,
+            Texture2 = 0x5,
+            Texture3 = 0x6,
+
+            PreviousBuffer = 0xd,
+            Constant = 0xe,
+            Previous = 0xf,
+        };
+
+        enum class ColorModifier : u32 {
+            SourceColor = 0x0,
+            OneMinusSourceColor = 0x1,
+            SourceAlpha = 0x2,
+            OneMinusSourceAlpha = 0x3,
+            SourceRed = 0x4,
+            OneMinusSourceRed = 0x5,
+
+            SourceGreen = 0x8,
+            OneMinusSourceGreen = 0x9,
+
+            SourceBlue = 0xc,
+            OneMinusSourceBlue = 0xd,
+        };
+
+        enum class AlphaModifier : u32 {
+            SourceAlpha = 0x0,
+            OneMinusSourceAlpha = 0x1,
+            SourceRed = 0x2,
+            OneMinusSourceRed = 0x3,
+            SourceGreen = 0x4,
+            OneMinusSourceGreen = 0x5,
+            SourceBlue = 0x6,
+            OneMinusSourceBlue = 0x7,
+        };
+
+        enum class Operation : u32 {
+            Replace = 0,
+            Modulate = 1,
+            Add = 2,
+            AddSigned = 3,
+            Lerp = 4,
+            Subtract = 5,
+            Dot3_RGB = 6,
+
+            MultiplyThenAdd = 8,
+            AddThenMultiply = 9,
+        };
+
+        union {
+            u32 sources_raw;
+            BitField<0, 4, Source> color_source1;
+            BitField<4, 4, Source> color_source2;
+            BitField<8, 4, Source> color_source3;
+            BitField<16, 4, Source> alpha_source1;
+            BitField<20, 4, Source> alpha_source2;
+            BitField<24, 4, Source> alpha_source3;
+        };
+
+        union {
+            u32 modifiers_raw;
+            BitField<0, 4, ColorModifier> color_modifier1;
+            BitField<4, 4, ColorModifier> color_modifier2;
+            BitField<8, 4, ColorModifier> color_modifier3;
+            BitField<12, 3, AlphaModifier> alpha_modifier1;
+            BitField<16, 3, AlphaModifier> alpha_modifier2;
+            BitField<20, 3, AlphaModifier> alpha_modifier3;
+        };
+
+        union {
+            u32 ops_raw;
+            BitField<0, 4, Operation> color_op;
+            BitField<16, 4, Operation> alpha_op;
+        };
+
+        union {
+            u32 const_color;
+            BitField<0, 8, u32> const_r;
+            BitField<8, 8, u32> const_g;
+            BitField<16, 8, u32> const_b;
+            BitField<24, 8, u32> const_a;
+        };
+
+        union {
+            u32 scales_raw;
+            BitField<0, 2, u32> color_scale;
+            BitField<16, 2, u32> alpha_scale;
+        };
+
+        inline unsigned GetColorMultiplier() const {
+            return (color_scale < 3) ? (1 << color_scale) : 1;
+        }
+
+        inline unsigned GetAlphaMultiplier() const {
+            return (alpha_scale < 3) ? (1 << alpha_scale) : 1;
+        }
+    };
+
+    TevStageConfig tev_stage0;
+    INSERT_PADDING_WORDS(0x3);
+    TevStageConfig tev_stage1;
+    INSERT_PADDING_WORDS(0x3);
+    TevStageConfig tev_stage2;
+    INSERT_PADDING_WORDS(0x3);
+    TevStageConfig tev_stage3;
+    INSERT_PADDING_WORDS(0x3);
+
+    enum class FogMode : u32 {
+        None = 0,
+        Fog = 5,
+        Gas = 7,
+    };
+
+    union {
+        BitField<0, 3, FogMode> fog_mode;
+        BitField<16, 1, u32> fog_flip;
+
+        union {
+            // Tev stages 0-3 write their output to the combiner buffer if the corresponding bit in
+            // these masks are set
+            BitField<8, 4, u32> update_mask_rgb;
+            BitField<12, 4, u32> update_mask_a;
+
+            bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
+                return (stage_index < 4) && (update_mask_rgb & (1 << stage_index));
+            }
+
+            bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const {
+                return (stage_index < 4) && (update_mask_a & (1 << stage_index));
+            }
+        } tev_combiner_buffer_input;
+    };
+
+    union {
+        u32 raw;
+        BitField<0, 8, u32> r;
+        BitField<8, 8, u32> g;
+        BitField<16, 8, u32> b;
+    } fog_color;
+
+    INSERT_PADDING_WORDS(0x4);
+
+    BitField<0, 16, u32> fog_lut_offset;
+
+    INSERT_PADDING_WORDS(0x1);
+
+    u32 fog_lut_data[8];
+
+    TevStageConfig tev_stage4;
+    INSERT_PADDING_WORDS(0x3);
+    TevStageConfig tev_stage5;
+
+    union {
+        u32 raw;
+        BitField<0, 8, u32> r;
+        BitField<8, 8, u32> g;
+        BitField<16, 8, u32> b;
+        BitField<24, 8, u32> a;
+    } tev_combiner_buffer_color;
+
+    INSERT_PADDING_WORDS(0x2);
+
+    const std::array<TevStageConfig, 6> GetTevStages() const {
+        return {{tev_stage0, tev_stage1, tev_stage2, tev_stage3, tev_stage4, tev_stage5}};
+    };
+};
+
+static_assert(sizeof(TexturingRegs) == 0x80 * sizeof(u32),
+              "TexturingRegs struct has incorrect size");
+
+} // namespace Pica
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 071e4ace0a..75736c99f4 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -14,8 +14,8 @@
 #include "common/microprofile.h"
 #include "common/vector_math.h"
 #include "core/hw/gpu.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
+#include "video_core/regs.h"
 #include "video_core/renderer_opengl/gl_rasterizer.h"
 #include "video_core/renderer_opengl/gl_shader_gen.h"
 #include "video_core/renderer_opengl/gl_shader_util.h"
@@ -26,13 +26,15 @@ MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
 MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255));
 MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
 
-static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) {
-    return (stage.color_op == Pica::Regs::TevStageConfig::Operation::Replace &&
-            stage.alpha_op == Pica::Regs::TevStageConfig::Operation::Replace &&
-            stage.color_source1 == Pica::Regs::TevStageConfig::Source::Previous &&
-            stage.alpha_source1 == Pica::Regs::TevStageConfig::Source::Previous &&
-            stage.color_modifier1 == Pica::Regs::TevStageConfig::ColorModifier::SourceColor &&
-            stage.alpha_modifier1 == Pica::Regs::TevStageConfig::AlphaModifier::SourceAlpha &&
+static bool IsPassThroughTevStage(const Pica::TexturingRegs::TevStageConfig& stage) {
+    using TevStageConfig = Pica::TexturingRegs::TevStageConfig;
+
+    return (stage.color_op == TevStageConfig::Operation::Replace &&
+            stage.alpha_op == TevStageConfig::Operation::Replace &&
+            stage.color_source1 == TevStageConfig::Source::Previous &&
+            stage.alpha_source1 == TevStageConfig::Source::Previous &&
+            stage.color_modifier1 == TevStageConfig::ColorModifier::SourceColor &&
+            stage.alpha_modifier1 == TevStageConfig::AlphaModifier::SourceAlpha &&
             stage.GetColorMultiplier() == 1 && stage.GetAlphaMultiplier() == 1);
 }
 
@@ -181,7 +183,7 @@ void RasterizerOpenGL::DrawTriangles() {
     CachedSurface* depth_surface;
     MathUtil::Rectangle<int> rect;
     std::tie(color_surface, depth_surface, rect) =
-        res_cache.GetFramebufferSurfaces(regs.framebuffer);
+        res_cache.GetFramebufferSurfaces(regs.framebuffer.framebuffer);
 
     state.draw.draw_framebuffer = framebuffer.handle;
     state.Apply();
@@ -190,20 +192,24 @@ void RasterizerOpenGL::DrawTriangles() {
                            color_surface != nullptr ? color_surface->texture.handle : 0, 0);
     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
                            depth_surface != nullptr ? depth_surface->texture.handle : 0, 0);
-    bool has_stencil = regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8;
+    bool has_stencil =
+        regs.framebuffer.framebuffer.depth_format == Pica::FramebufferRegs::DepthFormat::D24S8;
     glFramebufferTexture2D(
         GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
         (has_stencil && depth_surface != nullptr) ? depth_surface->texture.handle : 0, 0);
 
     // Sync the viewport
     // These registers hold half-width and half-height, so must be multiplied by 2
-    GLsizei viewport_width = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_x).ToFloat32() * 2;
-    GLsizei viewport_height = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_y).ToFloat32() * 2;
+    GLsizei viewport_width =
+        (GLsizei)Pica::float24::FromRaw(regs.rasterizer.viewport_size_x).ToFloat32() * 2;
+    GLsizei viewport_height =
+        (GLsizei)Pica::float24::FromRaw(regs.rasterizer.viewport_size_y).ToFloat32() * 2;
 
-    glViewport((GLint)(rect.left + regs.viewport_corner.x * color_surface->res_scale_width),
-               (GLint)(rect.bottom + regs.viewport_corner.y * color_surface->res_scale_height),
-               (GLsizei)(viewport_width * color_surface->res_scale_width),
-               (GLsizei)(viewport_height * color_surface->res_scale_height));
+    glViewport(
+        (GLint)(rect.left + regs.rasterizer.viewport_corner.x * color_surface->res_scale_width),
+        (GLint)(rect.bottom + regs.rasterizer.viewport_corner.y * color_surface->res_scale_height),
+        (GLsizei)(viewport_width * color_surface->res_scale_width),
+        (GLsizei)(viewport_height * color_surface->res_scale_height));
 
     if (uniform_block_data.data.framebuffer_scale[0] != color_surface->res_scale_width ||
         uniform_block_data.data.framebuffer_scale[1] != color_surface->res_scale_height) {
@@ -215,16 +221,16 @@ void RasterizerOpenGL::DrawTriangles() {
 
     // Scissor checks are window-, not viewport-relative, which means that if the cached texture
     // sub-rect changes, the scissor bounds also need to be updated.
-    GLint scissor_x1 =
-        static_cast<GLint>(rect.left + regs.scissor_test.x1 * color_surface->res_scale_width);
-    GLint scissor_y1 =
-        static_cast<GLint>(rect.bottom + regs.scissor_test.y1 * color_surface->res_scale_height);
+    GLint scissor_x1 = static_cast<GLint>(
+        rect.left + regs.rasterizer.scissor_test.x1 * color_surface->res_scale_width);
+    GLint scissor_y1 = static_cast<GLint>(
+        rect.bottom + regs.rasterizer.scissor_test.y1 * color_surface->res_scale_height);
     // x2, y2 have +1 added to cover the entire pixel area, otherwise you might get cracks when
     // scaling or doing multisampling.
-    GLint scissor_x2 =
-        static_cast<GLint>(rect.left + (regs.scissor_test.x2 + 1) * color_surface->res_scale_width);
+    GLint scissor_x2 = static_cast<GLint>(
+        rect.left + (regs.rasterizer.scissor_test.x2 + 1) * color_surface->res_scale_width);
     GLint scissor_y2 = static_cast<GLint>(
-        rect.bottom + (regs.scissor_test.y2 + 1) * color_surface->res_scale_height);
+        rect.bottom + (regs.rasterizer.scissor_test.y2 + 1) * color_surface->res_scale_height);
 
     if (uniform_block_data.data.scissor_x1 != scissor_x1 ||
         uniform_block_data.data.scissor_x2 != scissor_x2 ||
@@ -239,7 +245,7 @@ void RasterizerOpenGL::DrawTriangles() {
     }
 
     // Sync and bind the texture surfaces
-    const auto pica_textures = regs.GetTextures();
+    const auto pica_textures = regs.texturing.GetTextures();
     for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
         const auto& texture = pica_textures[texture_index];
 
@@ -316,69 +322,69 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
 
     switch (id) {
     // Culling
-    case PICA_REG_INDEX(cull_mode):
+    case PICA_REG_INDEX(rasterizer.cull_mode):
         SyncCullMode();
         break;
 
     // Depth modifiers
-    case PICA_REG_INDEX(viewport_depth_range):
+    case PICA_REG_INDEX(rasterizer.viewport_depth_range):
         SyncDepthScale();
         break;
-    case PICA_REG_INDEX(viewport_depth_near_plane):
+    case PICA_REG_INDEX(rasterizer.viewport_depth_near_plane):
         SyncDepthOffset();
         break;
 
     // Depth buffering
-    case PICA_REG_INDEX(depthmap_enable):
+    case PICA_REG_INDEX(rasterizer.depthmap_enable):
         shader_dirty = true;
         break;
 
     // Blending
-    case PICA_REG_INDEX(output_merger.alphablend_enable):
+    case PICA_REG_INDEX(framebuffer.output_merger.alphablend_enable):
         SyncBlendEnabled();
         break;
-    case PICA_REG_INDEX(output_merger.alpha_blending):
+    case PICA_REG_INDEX(framebuffer.output_merger.alpha_blending):
         SyncBlendFuncs();
         break;
-    case PICA_REG_INDEX(output_merger.blend_const):
+    case PICA_REG_INDEX(framebuffer.output_merger.blend_const):
         SyncBlendColor();
         break;
 
     // Fog state
-    case PICA_REG_INDEX(fog_color):
+    case PICA_REG_INDEX(texturing.fog_color):
         SyncFogColor();
         break;
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[0], 0xe8):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[1], 0xe9):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[2], 0xea):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[3], 0xeb):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[4], 0xec):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[5], 0xed):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[6], 0xee):
-    case PICA_REG_INDEX_WORKAROUND(fog_lut_data[7], 0xef):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[0], 0xe8):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[1], 0xe9):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[2], 0xea):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[3], 0xeb):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[4], 0xec):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[5], 0xed):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[6], 0xee):
+    case PICA_REG_INDEX_WORKAROUND(texturing.fog_lut_data[7], 0xef):
         uniform_block_data.fog_lut_dirty = true;
         break;
 
     // Alpha test
-    case PICA_REG_INDEX(output_merger.alpha_test):
+    case PICA_REG_INDEX(framebuffer.output_merger.alpha_test):
         SyncAlphaTest();
         shader_dirty = true;
         break;
 
     // Sync GL stencil test + stencil write mask
     // (Pica stencil test function register also contains a stencil write mask)
-    case PICA_REG_INDEX(output_merger.stencil_test.raw_func):
+    case PICA_REG_INDEX(framebuffer.output_merger.stencil_test.raw_func):
         SyncStencilTest();
         SyncStencilWriteMask();
         break;
-    case PICA_REG_INDEX(output_merger.stencil_test.raw_op):
-    case PICA_REG_INDEX(framebuffer.depth_format):
+    case PICA_REG_INDEX(framebuffer.output_merger.stencil_test.raw_op):
+    case PICA_REG_INDEX(framebuffer.framebuffer.depth_format):
         SyncStencilTest();
         break;
 
     // Sync GL depth test + depth and color write mask
     // (Pica depth test function register also contains a depth and color write mask)
-    case PICA_REG_INDEX(output_merger.depth_test_enable):
+    case PICA_REG_INDEX(framebuffer.output_merger.depth_test_enable):
         SyncDepthTest();
         SyncDepthWriteMask();
         SyncColorWriteMask();
@@ -386,82 +392,82 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
 
     // Sync GL depth and stencil write mask
     // (This is a dedicated combined depth / stencil write-enable register)
-    case PICA_REG_INDEX(framebuffer.allow_depth_stencil_write):
+    case PICA_REG_INDEX(framebuffer.framebuffer.allow_depth_stencil_write):
         SyncDepthWriteMask();
         SyncStencilWriteMask();
         break;
 
     // Sync GL color write mask
     // (This is a dedicated color write-enable register)
-    case PICA_REG_INDEX(framebuffer.allow_color_write):
+    case PICA_REG_INDEX(framebuffer.framebuffer.allow_color_write):
         SyncColorWriteMask();
         break;
 
     // Scissor test
-    case PICA_REG_INDEX(scissor_test.mode):
+    case PICA_REG_INDEX(rasterizer.scissor_test.mode):
         shader_dirty = true;
         break;
 
     // Logic op
-    case PICA_REG_INDEX(output_merger.logic_op):
+    case PICA_REG_INDEX(framebuffer.output_merger.logic_op):
         SyncLogicOp();
         break;
 
     // Texture 0 type
-    case PICA_REG_INDEX(texture0.type):
+    case PICA_REG_INDEX(texturing.texture0.type):
         shader_dirty = true;
         break;
 
     // TEV stages
     // (This also syncs fog_mode and fog_flip which are part of tev_combiner_buffer_input)
-    case PICA_REG_INDEX(tev_stage0.color_source1):
-    case PICA_REG_INDEX(tev_stage0.color_modifier1):
-    case PICA_REG_INDEX(tev_stage0.color_op):
-    case PICA_REG_INDEX(tev_stage0.color_scale):
-    case PICA_REG_INDEX(tev_stage1.color_source1):
-    case PICA_REG_INDEX(tev_stage1.color_modifier1):
-    case PICA_REG_INDEX(tev_stage1.color_op):
-    case PICA_REG_INDEX(tev_stage1.color_scale):
-    case PICA_REG_INDEX(tev_stage2.color_source1):
-    case PICA_REG_INDEX(tev_stage2.color_modifier1):
-    case PICA_REG_INDEX(tev_stage2.color_op):
-    case PICA_REG_INDEX(tev_stage2.color_scale):
-    case PICA_REG_INDEX(tev_stage3.color_source1):
-    case PICA_REG_INDEX(tev_stage3.color_modifier1):
-    case PICA_REG_INDEX(tev_stage3.color_op):
-    case PICA_REG_INDEX(tev_stage3.color_scale):
-    case PICA_REG_INDEX(tev_stage4.color_source1):
-    case PICA_REG_INDEX(tev_stage4.color_modifier1):
-    case PICA_REG_INDEX(tev_stage4.color_op):
-    case PICA_REG_INDEX(tev_stage4.color_scale):
-    case PICA_REG_INDEX(tev_stage5.color_source1):
-    case PICA_REG_INDEX(tev_stage5.color_modifier1):
-    case PICA_REG_INDEX(tev_stage5.color_op):
-    case PICA_REG_INDEX(tev_stage5.color_scale):
-    case PICA_REG_INDEX(tev_combiner_buffer_input):
+    case PICA_REG_INDEX(texturing.tev_stage0.color_source1):
+    case PICA_REG_INDEX(texturing.tev_stage0.color_modifier1):
+    case PICA_REG_INDEX(texturing.tev_stage0.color_op):
+    case PICA_REG_INDEX(texturing.tev_stage0.color_scale):
+    case PICA_REG_INDEX(texturing.tev_stage1.color_source1):
+    case PICA_REG_INDEX(texturing.tev_stage1.color_modifier1):
+    case PICA_REG_INDEX(texturing.tev_stage1.color_op):
+    case PICA_REG_INDEX(texturing.tev_stage1.color_scale):
+    case PICA_REG_INDEX(texturing.tev_stage2.color_source1):
+    case PICA_REG_INDEX(texturing.tev_stage2.color_modifier1):
+    case PICA_REG_INDEX(texturing.tev_stage2.color_op):
+    case PICA_REG_INDEX(texturing.tev_stage2.color_scale):
+    case PICA_REG_INDEX(texturing.tev_stage3.color_source1):
+    case PICA_REG_INDEX(texturing.tev_stage3.color_modifier1):
+    case PICA_REG_INDEX(texturing.tev_stage3.color_op):
+    case PICA_REG_INDEX(texturing.tev_stage3.color_scale):
+    case PICA_REG_INDEX(texturing.tev_stage4.color_source1):
+    case PICA_REG_INDEX(texturing.tev_stage4.color_modifier1):
+    case PICA_REG_INDEX(texturing.tev_stage4.color_op):
+    case PICA_REG_INDEX(texturing.tev_stage4.color_scale):
+    case PICA_REG_INDEX(texturing.tev_stage5.color_source1):
+    case PICA_REG_INDEX(texturing.tev_stage5.color_modifier1):
+    case PICA_REG_INDEX(texturing.tev_stage5.color_op):
+    case PICA_REG_INDEX(texturing.tev_stage5.color_scale):
+    case PICA_REG_INDEX(texturing.tev_combiner_buffer_input):
         shader_dirty = true;
         break;
-    case PICA_REG_INDEX(tev_stage0.const_r):
-        SyncTevConstColor(0, regs.tev_stage0);
+    case PICA_REG_INDEX(texturing.tev_stage0.const_r):
+        SyncTevConstColor(0, regs.texturing.tev_stage0);
         break;
-    case PICA_REG_INDEX(tev_stage1.const_r):
-        SyncTevConstColor(1, regs.tev_stage1);
+    case PICA_REG_INDEX(texturing.tev_stage1.const_r):
+        SyncTevConstColor(1, regs.texturing.tev_stage1);
         break;
-    case PICA_REG_INDEX(tev_stage2.const_r):
-        SyncTevConstColor(2, regs.tev_stage2);
+    case PICA_REG_INDEX(texturing.tev_stage2.const_r):
+        SyncTevConstColor(2, regs.texturing.tev_stage2);
         break;
-    case PICA_REG_INDEX(tev_stage3.const_r):
-        SyncTevConstColor(3, regs.tev_stage3);
+    case PICA_REG_INDEX(texturing.tev_stage3.const_r):
+        SyncTevConstColor(3, regs.texturing.tev_stage3);
         break;
-    case PICA_REG_INDEX(tev_stage4.const_r):
-        SyncTevConstColor(4, regs.tev_stage4);
+    case PICA_REG_INDEX(texturing.tev_stage4.const_r):
+        SyncTevConstColor(4, regs.texturing.tev_stage4);
         break;
-    case PICA_REG_INDEX(tev_stage5.const_r):
-        SyncTevConstColor(5, regs.tev_stage5);
+    case PICA_REG_INDEX(texturing.tev_stage5.const_r):
+        SyncTevConstColor(5, regs.texturing.tev_stage5);
         break;
 
     // TEV combiner buffer color
-    case PICA_REG_INDEX(tev_combiner_buffer_color):
+    case PICA_REG_INDEX(texturing.tev_combiner_buffer_color):
         SyncCombinerColor();
         break;
 
@@ -976,7 +982,9 @@ void RasterizerOpenGL::SamplerInfo::Create() {
     // Other attributes have correct defaults
 }
 
-void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Pica::Regs::TextureConfig& config) {
+void RasterizerOpenGL::SamplerInfo::SyncWithConfig(
+    const Pica::TexturingRegs::TextureConfig& config) {
+
     GLuint s = sampler.handle;
 
     if (mag_filter != config.mag_filter) {
@@ -1088,7 +1096,7 @@ void RasterizerOpenGL::SetShader() {
         SyncDepthOffset();
         SyncAlphaTest();
         SyncCombinerColor();
-        auto& tev_stages = Pica::g_state.regs.GetTevStages();
+        auto& tev_stages = Pica::g_state.regs.texturing.GetTevStages();
         for (int index = 0; index < tev_stages.size(); ++index)
             SyncTevConstColor(index, tev_stages[index]);
 
@@ -1110,30 +1118,31 @@ void RasterizerOpenGL::SetShader() {
 void RasterizerOpenGL::SyncCullMode() {
     const auto& regs = Pica::g_state.regs;
 
-    switch (regs.cull_mode) {
-    case Pica::Regs::CullMode::KeepAll:
+    switch (regs.rasterizer.cull_mode) {
+    case Pica::RasterizerRegs::CullMode::KeepAll:
         state.cull.enabled = false;
         break;
 
-    case Pica::Regs::CullMode::KeepClockWise:
+    case Pica::RasterizerRegs::CullMode::KeepClockWise:
         state.cull.enabled = true;
         state.cull.front_face = GL_CW;
         break;
 
-    case Pica::Regs::CullMode::KeepCounterClockWise:
+    case Pica::RasterizerRegs::CullMode::KeepCounterClockWise:
         state.cull.enabled = true;
         state.cull.front_face = GL_CCW;
         break;
 
     default:
-        LOG_CRITICAL(Render_OpenGL, "Unknown cull mode %d", regs.cull_mode.Value());
+        LOG_CRITICAL(Render_OpenGL, "Unknown cull mode %d", regs.rasterizer.cull_mode.Value());
         UNIMPLEMENTED();
         break;
     }
 }
 
 void RasterizerOpenGL::SyncDepthScale() {
-    float depth_scale = Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_range).ToFloat32();
+    float depth_scale =
+        Pica::float24::FromRaw(Pica::g_state.regs.rasterizer.viewport_depth_range).ToFloat32();
     if (depth_scale != uniform_block_data.data.depth_scale) {
         uniform_block_data.data.depth_scale = depth_scale;
         uniform_block_data.dirty = true;
@@ -1142,7 +1151,7 @@ void RasterizerOpenGL::SyncDepthScale() {
 
 void RasterizerOpenGL::SyncDepthOffset() {
     float depth_offset =
-        Pica::float24::FromRaw(Pica::g_state.regs.viewport_depth_near_plane).ToFloat32();
+        Pica::float24::FromRaw(Pica::g_state.regs.rasterizer.viewport_depth_near_plane).ToFloat32();
     if (depth_offset != uniform_block_data.data.depth_offset) {
         uniform_block_data.data.depth_offset = depth_offset;
         uniform_block_data.dirty = true;
@@ -1150,25 +1159,28 @@ void RasterizerOpenGL::SyncDepthOffset() {
 }
 
 void RasterizerOpenGL::SyncBlendEnabled() {
-    state.blend.enabled = (Pica::g_state.regs.output_merger.alphablend_enable == 1);
+    state.blend.enabled = (Pica::g_state.regs.framebuffer.output_merger.alphablend_enable == 1);
 }
 
 void RasterizerOpenGL::SyncBlendFuncs() {
     const auto& regs = Pica::g_state.regs;
     state.blend.rgb_equation =
-        PicaToGL::BlendEquation(regs.output_merger.alpha_blending.blend_equation_rgb);
+        PicaToGL::BlendEquation(regs.framebuffer.output_merger.alpha_blending.blend_equation_rgb);
     state.blend.a_equation =
-        PicaToGL::BlendEquation(regs.output_merger.alpha_blending.blend_equation_a);
+        PicaToGL::BlendEquation(regs.framebuffer.output_merger.alpha_blending.blend_equation_a);
     state.blend.src_rgb_func =
-        PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_source_rgb);
+        PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_source_rgb);
     state.blend.dst_rgb_func =
-        PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_dest_rgb);
-    state.blend.src_a_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_source_a);
-    state.blend.dst_a_func = PicaToGL::BlendFunc(regs.output_merger.alpha_blending.factor_dest_a);
+        PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_dest_rgb);
+    state.blend.src_a_func =
+        PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_source_a);
+    state.blend.dst_a_func =
+        PicaToGL::BlendFunc(regs.framebuffer.output_merger.alpha_blending.factor_dest_a);
 }
 
 void RasterizerOpenGL::SyncBlendColor() {
-    auto blend_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.output_merger.blend_const.raw);
+    auto blend_color =
+        PicaToGL::ColorRGBA8(Pica::g_state.regs.framebuffer.output_merger.blend_const.raw);
     state.blend.color.red = blend_color[0];
     state.blend.color.green = blend_color[1];
     state.blend.color.blue = blend_color[2];
@@ -1178,8 +1190,8 @@ void RasterizerOpenGL::SyncBlendColor() {
 void RasterizerOpenGL::SyncFogColor() {
     const auto& regs = Pica::g_state.regs;
     uniform_block_data.data.fog_color = {
-        regs.fog_color.r.Value() / 255.0f, regs.fog_color.g.Value() / 255.0f,
-        regs.fog_color.b.Value() / 255.0f,
+        regs.texturing.fog_color.r.Value() / 255.0f, regs.texturing.fog_color.g.Value() / 255.0f,
+        regs.texturing.fog_color.b.Value() / 255.0f,
     };
     uniform_block_data.dirty = true;
 }
@@ -1200,70 +1212,78 @@ void RasterizerOpenGL::SyncFogLUT() {
 
 void RasterizerOpenGL::SyncAlphaTest() {
     const auto& regs = Pica::g_state.regs;
-    if (regs.output_merger.alpha_test.ref != uniform_block_data.data.alphatest_ref) {
-        uniform_block_data.data.alphatest_ref = regs.output_merger.alpha_test.ref;
+    if (regs.framebuffer.output_merger.alpha_test.ref != uniform_block_data.data.alphatest_ref) {
+        uniform_block_data.data.alphatest_ref = regs.framebuffer.output_merger.alpha_test.ref;
         uniform_block_data.dirty = true;
     }
 }
 
 void RasterizerOpenGL::SyncLogicOp() {
-    state.logic_op = PicaToGL::LogicOp(Pica::g_state.regs.output_merger.logic_op);
+    state.logic_op = PicaToGL::LogicOp(Pica::g_state.regs.framebuffer.output_merger.logic_op);
 }
 
 void RasterizerOpenGL::SyncColorWriteMask() {
     const auto& regs = Pica::g_state.regs;
 
     auto IsColorWriteEnabled = [&](u32 value) {
-        return (regs.framebuffer.allow_color_write != 0 && value != 0) ? GL_TRUE : GL_FALSE;
+        return (regs.framebuffer.framebuffer.allow_color_write != 0 && value != 0) ? GL_TRUE
+                                                                                   : GL_FALSE;
     };
 
-    state.color_mask.red_enabled = IsColorWriteEnabled(regs.output_merger.red_enable);
-    state.color_mask.green_enabled = IsColorWriteEnabled(regs.output_merger.green_enable);
-    state.color_mask.blue_enabled = IsColorWriteEnabled(regs.output_merger.blue_enable);
-    state.color_mask.alpha_enabled = IsColorWriteEnabled(regs.output_merger.alpha_enable);
+    state.color_mask.red_enabled = IsColorWriteEnabled(regs.framebuffer.output_merger.red_enable);
+    state.color_mask.green_enabled =
+        IsColorWriteEnabled(regs.framebuffer.output_merger.green_enable);
+    state.color_mask.blue_enabled = IsColorWriteEnabled(regs.framebuffer.output_merger.blue_enable);
+    state.color_mask.alpha_enabled =
+        IsColorWriteEnabled(regs.framebuffer.output_merger.alpha_enable);
 }
 
 void RasterizerOpenGL::SyncStencilWriteMask() {
     const auto& regs = Pica::g_state.regs;
-    state.stencil.write_mask = (regs.framebuffer.allow_depth_stencil_write != 0)
-                                   ? static_cast<GLuint>(regs.output_merger.stencil_test.write_mask)
-                                   : 0;
+    state.stencil.write_mask =
+        (regs.framebuffer.framebuffer.allow_depth_stencil_write != 0)
+            ? static_cast<GLuint>(regs.framebuffer.output_merger.stencil_test.write_mask)
+            : 0;
 }
 
 void RasterizerOpenGL::SyncDepthWriteMask() {
     const auto& regs = Pica::g_state.regs;
-    state.depth.write_mask =
-        (regs.framebuffer.allow_depth_stencil_write != 0 && regs.output_merger.depth_write_enable)
-            ? GL_TRUE
-            : GL_FALSE;
+    state.depth.write_mask = (regs.framebuffer.framebuffer.allow_depth_stencil_write != 0 &&
+                              regs.framebuffer.output_merger.depth_write_enable)
+                                 ? GL_TRUE
+                                 : GL_FALSE;
 }
 
 void RasterizerOpenGL::SyncStencilTest() {
     const auto& regs = Pica::g_state.regs;
-    state.stencil.test_enabled = regs.output_merger.stencil_test.enable &&
-                                 regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8;
-    state.stencil.test_func = PicaToGL::CompareFunc(regs.output_merger.stencil_test.func);
-    state.stencil.test_ref = regs.output_merger.stencil_test.reference_value;
-    state.stencil.test_mask = regs.output_merger.stencil_test.input_mask;
+    state.stencil.test_enabled =
+        regs.framebuffer.output_merger.stencil_test.enable &&
+        regs.framebuffer.framebuffer.depth_format == Pica::FramebufferRegs::DepthFormat::D24S8;
+    state.stencil.test_func =
+        PicaToGL::CompareFunc(regs.framebuffer.output_merger.stencil_test.func);
+    state.stencil.test_ref = regs.framebuffer.output_merger.stencil_test.reference_value;
+    state.stencil.test_mask = regs.framebuffer.output_merger.stencil_test.input_mask;
     state.stencil.action_stencil_fail =
-        PicaToGL::StencilOp(regs.output_merger.stencil_test.action_stencil_fail);
+        PicaToGL::StencilOp(regs.framebuffer.output_merger.stencil_test.action_stencil_fail);
     state.stencil.action_depth_fail =
-        PicaToGL::StencilOp(regs.output_merger.stencil_test.action_depth_fail);
+        PicaToGL::StencilOp(regs.framebuffer.output_merger.stencil_test.action_depth_fail);
     state.stencil.action_depth_pass =
-        PicaToGL::StencilOp(regs.output_merger.stencil_test.action_depth_pass);
+        PicaToGL::StencilOp(regs.framebuffer.output_merger.stencil_test.action_depth_pass);
 }
 
 void RasterizerOpenGL::SyncDepthTest() {
     const auto& regs = Pica::g_state.regs;
-    state.depth.test_enabled =
-        regs.output_merger.depth_test_enable == 1 || regs.output_merger.depth_write_enable == 1;
-    state.depth.test_func = regs.output_merger.depth_test_enable == 1
-                                ? PicaToGL::CompareFunc(regs.output_merger.depth_test_func)
-                                : GL_ALWAYS;
+    state.depth.test_enabled = regs.framebuffer.output_merger.depth_test_enable == 1 ||
+                               regs.framebuffer.output_merger.depth_write_enable == 1;
+    state.depth.test_func =
+        regs.framebuffer.output_merger.depth_test_enable == 1
+            ? PicaToGL::CompareFunc(regs.framebuffer.output_merger.depth_test_func)
+            : GL_ALWAYS;
 }
 
 void RasterizerOpenGL::SyncCombinerColor() {
-    auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw);
+    auto combiner_color =
+        PicaToGL::ColorRGBA8(Pica::g_state.regs.texturing.tev_combiner_buffer_color.raw);
     if (combiner_color != uniform_block_data.data.tev_combiner_buffer_color) {
         uniform_block_data.data.tev_combiner_buffer_color = combiner_color;
         uniform_block_data.dirty = true;
@@ -1271,7 +1291,7 @@ void RasterizerOpenGL::SyncCombinerColor() {
 }
 
 void RasterizerOpenGL::SyncTevConstColor(int stage_index,
-                                         const Pica::Regs::TevStageConfig& tev_stage) {
+                                         const Pica::TexturingRegs::TevStageConfig& tev_stage) {
     auto const_color = PicaToGL::ColorRGBA8(tev_stage.const_color);
     if (const_color != uniform_block_data.data.const_color[stage_index]) {
         uniform_block_data.data.const_color[stage_index] = const_color;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index a1aa07074e..bfee911b6d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -16,10 +16,10 @@
 #include "common/hash.h"
 #include "common/vector_math.h"
 #include "core/hw/gpu.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
 #include "video_core/pica_types.h"
 #include "video_core/rasterizer_interface.h"
+#include "video_core/regs.h"
 #include "video_core/renderer_opengl/gl_rasterizer_cache.h"
 #include "video_core/renderer_opengl/gl_resource_manager.h"
 #include "video_core/renderer_opengl/gl_state.h"
@@ -52,20 +52,20 @@ union PicaShaderConfig {
 
         const auto& regs = Pica::g_state.regs;
 
-        state.scissor_test_mode = regs.scissor_test.mode;
+        state.scissor_test_mode = regs.rasterizer.scissor_test.mode;
 
-        state.depthmap_enable = regs.depthmap_enable;
+        state.depthmap_enable = regs.rasterizer.depthmap_enable;
 
-        state.alpha_test_func = regs.output_merger.alpha_test.enable
-                                    ? regs.output_merger.alpha_test.func.Value()
-                                    : Pica::Regs::CompareFunc::Always;
+        state.alpha_test_func = regs.framebuffer.output_merger.alpha_test.enable
+                                    ? regs.framebuffer.output_merger.alpha_test.func.Value()
+                                    : Pica::FramebufferRegs::CompareFunc::Always;
 
-        state.texture0_type = regs.texture0.type;
+        state.texture0_type = regs.texturing.texture0.type;
 
         // Copy relevant tev stages fields.
         // We don't sync const_color here because of the high variance, it is a
         // shader uniform instead.
-        const auto& tev_stages = regs.GetTevStages();
+        const auto& tev_stages = regs.texturing.GetTevStages();
         DEBUG_ASSERT(state.tev_stages.size() == tev_stages.size());
         for (size_t i = 0; i < tev_stages.size(); i++) {
             const auto& tev_stage = tev_stages[i];
@@ -75,11 +75,12 @@ union PicaShaderConfig {
             state.tev_stages[i].scales_raw = tev_stage.scales_raw;
         }
 
-        state.fog_mode = regs.fog_mode;
-        state.fog_flip = regs.fog_flip != 0;
+        state.fog_mode = regs.texturing.fog_mode;
+        state.fog_flip = regs.texturing.fog_flip != 0;
 
-        state.combiner_buffer_input = regs.tev_combiner_buffer_input.update_mask_rgb.Value() |
-                                      regs.tev_combiner_buffer_input.update_mask_a.Value() << 4;
+        state.combiner_buffer_input =
+            regs.texturing.tev_combiner_buffer_input.update_mask_rgb.Value() |
+            regs.texturing.tev_combiner_buffer_input.update_mask_a.Value() << 4;
 
         // Fragment lighting
 
@@ -159,8 +160,8 @@ union PicaShaderConfig {
         u32 modifiers_raw;
         u32 ops_raw;
         u32 scales_raw;
-        explicit operator Pica::Regs::TevStageConfig() const noexcept {
-            Pica::Regs::TevStageConfig stage;
+        explicit operator Pica::TexturingRegs::TevStageConfig() const noexcept {
+            Pica::TexturingRegs::TevStageConfig stage;
             stage.sources_raw = sources_raw;
             stage.modifiers_raw = modifiers_raw;
             stage.ops_raw = ops_raw;
@@ -171,14 +172,14 @@ union PicaShaderConfig {
     };
 
     struct State {
-        Pica::Regs::CompareFunc alpha_test_func;
-        Pica::Regs::ScissorMode scissor_test_mode;
-        Pica::Regs::TextureConfig::TextureType texture0_type;
+        Pica::FramebufferRegs::CompareFunc alpha_test_func;
+        Pica::RasterizerRegs::ScissorMode scissor_test_mode;
+        Pica::TexturingRegs::TextureConfig::TextureType texture0_type;
         std::array<TevStageConfigRaw, 6> tev_stages;
         u8 combiner_buffer_input;
 
-        Pica::Regs::DepthBuffering depthmap_enable;
-        Pica::Regs::FogMode fog_mode;
+        Pica::RasterizerRegs::DepthBuffering depthmap_enable;
+        Pica::TexturingRegs::FogMode fog_mode;
         bool fog_flip;
 
         struct {
@@ -191,18 +192,18 @@ union PicaShaderConfig {
 
             bool enable;
             unsigned src_num;
-            Pica::Regs::LightingBumpMode bump_mode;
+            Pica::LightingRegs::LightingBumpMode bump_mode;
             unsigned bump_selector;
             bool bump_renorm;
             bool clamp_highlights;
 
-            Pica::Regs::LightingConfig config;
-            Pica::Regs::LightingFresnelSelector fresnel_selector;
+            Pica::LightingRegs::LightingConfig config;
+            Pica::LightingRegs::LightingFresnelSelector fresnel_selector;
 
             struct {
                 bool enable;
                 bool abs_input;
-                Pica::Regs::LightingLutInput type;
+                Pica::LightingRegs::LightingLutInput type;
                 float scale;
             } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb;
         } lighting;
@@ -251,7 +252,7 @@ public:
 
 private:
     struct SamplerInfo {
-        using TextureConfig = Pica::Regs::TextureConfig;
+        using TextureConfig = Pica::TexturingRegs::TextureConfig;
 
         OGLSampler sampler;
 
@@ -398,7 +399,7 @@ private:
     void SyncCombinerColor();
 
     /// Syncs the TEV constant color to match the PICA register
-    void SyncTevConstColor(int tev_index, const Pica::Regs::TevStageConfig& tev_stage);
+    void SyncTevConstColor(int tev_index, const Pica::TexturingRegs::TevStageConfig& tev_stage);
 
     /// Syncs the lighting global ambient color to match the PICA register
     void SyncGlobalAmbient();
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 60380257a6..0818a87b33 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -342,7 +342,7 @@ CachedSurface* RasterizerCacheOpenGL::GetSurface(const CachedSurface& params, bo
                 Pica::Texture::TextureInfo tex_info;
                 tex_info.width = params.width;
                 tex_info.height = params.height;
-                tex_info.format = (Pica::Regs::TextureFormat)params.pixel_format;
+                tex_info.format = (Pica::TexturingRegs::TextureFormat)params.pixel_format;
                 tex_info.SetDefaultStride();
                 tex_info.physical_address = params.addr;
 
@@ -510,7 +510,7 @@ CachedSurface* RasterizerCacheOpenGL::GetSurfaceRect(const CachedSurface& params
 }
 
 CachedSurface* RasterizerCacheOpenGL::GetTextureSurface(
-    const Pica::Regs::FullTextureConfig& config) {
+    const Pica::TexturingRegs::FullTextureConfig& config) {
 
     Pica::Texture::TextureInfo info =
         Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format);
@@ -525,7 +525,9 @@ CachedSurface* RasterizerCacheOpenGL::GetTextureSurface(
 }
 
 std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>>
-RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfig& config) {
+RasterizerCacheOpenGL::GetFramebufferSurfaces(
+    const Pica::FramebufferRegs::FramebufferConfig& config) {
+
     const auto& regs = Pica::g_state.regs;
 
     // Make sur that framebuffers don't overlap if both color and depth are being used
@@ -537,11 +539,12 @@ RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfi
             config.GetColorBufferPhysicalAddress(),
             fb_area * GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(config.color_format.Value())),
             config.GetDepthBufferPhysicalAddress(),
-            fb_area * Pica::Regs::BytesPerDepthPixel(config.depth_format));
+            fb_area * Pica::FramebufferRegs::BytesPerDepthPixel(config.depth_format));
     bool using_color_fb = config.GetColorBufferPhysicalAddress() != 0;
-    bool using_depth_fb = config.GetDepthBufferPhysicalAddress() != 0 &&
-                          (regs.output_merger.depth_test_enable ||
-                           regs.output_merger.depth_write_enable || !framebuffers_overlap);
+    bool using_depth_fb =
+        config.GetDepthBufferPhysicalAddress() != 0 &&
+        (regs.framebuffer.output_merger.depth_test_enable ||
+         regs.framebuffer.output_merger.depth_write_enable || !framebuffers_overlap);
 
     if (framebuffers_overlap && using_color_fb && using_depth_fb) {
         LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; "
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index f57fdb3cc1..4072ed49ed 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -21,7 +21,7 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "core/hw/gpu.h"
-#include "video_core/pica.h"
+#include "video_core/regs.h"
 #include "video_core/renderer_opengl/gl_resource_manager.h"
 
 namespace MathUtil {
@@ -96,15 +96,15 @@ struct CachedSurface {
         return bpp_table[(unsigned int)format];
     }
 
-    static PixelFormat PixelFormatFromTextureFormat(Pica::Regs::TextureFormat format) {
+    static PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) {
         return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid;
     }
 
-    static PixelFormat PixelFormatFromColorFormat(Pica::Regs::ColorFormat format) {
+    static PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) {
         return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid;
     }
 
-    static PixelFormat PixelFormatFromDepthFormat(Pica::Regs::DepthFormat format) {
+    static PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) {
         return ((unsigned int)format < 4) ? (PixelFormat)((unsigned int)format + 14)
                                           : PixelFormat::Invalid;
     }
@@ -212,12 +212,12 @@ public:
                                   bool load_if_create, MathUtil::Rectangle<int>& out_rect);
 
     /// Gets a surface based on the texture configuration
-    CachedSurface* GetTextureSurface(const Pica::Regs::FullTextureConfig& config);
+    CachedSurface* GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config);
 
     /// Gets the color and depth surfaces and rect (resolution scaled) based on the framebuffer
     /// configuration
     std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> GetFramebufferSurfaces(
-        const Pica::Regs::FramebufferConfig& config);
+        const Pica::FramebufferRegs::FramebufferConfig& config);
 
     /// Attempt to get a surface that exactly matches the fill region and format
     CachedSurface* TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config);
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 4c4f98ac9b..3ea25f3026 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -7,13 +7,15 @@
 #include "common/assert.h"
 #include "common/bit_field.h"
 #include "common/logging/log.h"
-#include "video_core/pica.h"
+#include "video_core/regs.h"
 #include "video_core/renderer_opengl/gl_rasterizer.h"
 #include "video_core/renderer_opengl/gl_shader_gen.h"
 #include "video_core/renderer_opengl/gl_shader_util.h"
 
 using Pica::Regs;
-using TevStageConfig = Regs::TevStageConfig;
+using Pica::RasterizerRegs;
+using Pica::LightingRegs;
+using TevStageConfig = Pica::TexturingRegs::TevStageConfig;
 
 namespace GLShader {
 
@@ -46,10 +48,10 @@ static void AppendSource(std::string& out, const PicaShaderConfig& config,
     case Source::Texture0:
         // Only unit 0 respects the texturing type (according to 3DBrew)
         switch (state.texture0_type) {
-        case Pica::Regs::TextureConfig::Texture2D:
+        case Pica::TexturingRegs::TextureConfig::Texture2D:
             out += "texture(tex[0], texcoord[0])";
             break;
-        case Pica::Regs::TextureConfig::Projection2D:
+        case Pica::TexturingRegs::TextureConfig::Projection2D:
             out += "textureProj(tex[0], vec3(texcoord[0], texcoord0_w))";
             break;
         default:
@@ -276,8 +278,8 @@ static void AppendAlphaCombiner(std::string& out, TevStageConfig::Operation oper
 }
 
 /// Writes the if-statement condition used to evaluate alpha testing
-static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) {
-    using CompareFunc = Regs::CompareFunc;
+static void AppendAlphaTestCondition(std::string& out, Pica::FramebufferRegs::CompareFunc func) {
+    using CompareFunc = Pica::FramebufferRegs::CompareFunc;
     switch (func) {
     case CompareFunc::Never:
         out += "true";
@@ -307,7 +309,7 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) {
 /// Writes the code to emulate the specified TEV stage
 static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsigned index) {
     const auto stage =
-        static_cast<const Pica::Regs::TevStageConfig>(config.state.tev_stages[index]);
+        static_cast<const Pica::TexturingRegs::TevStageConfig>(config.state.tev_stages[index]);
     if (!IsPassThroughTevStage(stage)) {
         std::string index_name = std::to_string(index);
 
@@ -364,7 +366,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
            "vec3 refl_value = vec3(0.0);\n";
 
     // Compute fragment normals
-    if (lighting.bump_mode == Pica::Regs::LightingBumpMode::NormalMap) {
+    if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {
         // Bump mapping is enabled using a normal map, read perturbation vector from the selected
         // texture
         std::string bump_selector = std::to_string(lighting.bump_selector);
@@ -378,7 +380,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
                 "(1.0 - (surface_normal.x*surface_normal.x + surface_normal.y*surface_normal.y))";
             out += "surface_normal.z = sqrt(max(" + val + ", 0.0));\n";
         }
-    } else if (lighting.bump_mode == Pica::Regs::LightingBumpMode::TangentMap) {
+    } else if (lighting.bump_mode == LightingRegs::LightingBumpMode::TangentMap) {
         // Bump mapping is enabled using a tangent map
         LOG_CRITICAL(HW_GPU, "unimplemented bump mapping mode (tangent mapping)");
         UNIMPLEMENTED();
@@ -392,23 +394,24 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
     out += "vec3 normal = normalize(quaternion_rotate(normquat, surface_normal));\n";
 
     // Gets the index into the specified lookup table for specular lighting
-    auto GetLutIndex = [&lighting](unsigned light_num, Regs::LightingLutInput input, bool abs) {
+    auto GetLutIndex = [&lighting](unsigned light_num, LightingRegs::LightingLutInput input,
+                                   bool abs) {
         const std::string half_angle = "normalize(normalize(view) + light_vector)";
         std::string index;
         switch (input) {
-        case Regs::LightingLutInput::NH:
+        case LightingRegs::LightingLutInput::NH:
             index = "dot(normal, " + half_angle + ")";
             break;
 
-        case Regs::LightingLutInput::VH:
+        case LightingRegs::LightingLutInput::VH:
             index = std::string("dot(normalize(view), " + half_angle + ")");
             break;
 
-        case Regs::LightingLutInput::NV:
+        case LightingRegs::LightingLutInput::NV:
             index = std::string("dot(normal, normalize(view))");
             break;
 
-        case Regs::LightingLutInput::LN:
+        case LightingRegs::LightingLutInput::LN:
             index = std::string("dot(light_vector, normal)");
             break;
 
@@ -432,7 +435,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
     };
 
     // Gets the lighting lookup table value given the specified sampler and index
-    auto GetLutValue = [](Regs::LightingSampler sampler, std::string lut_index) {
+    auto GetLutValue = [](LightingRegs::LightingSampler sampler, std::string lut_index) {
         return std::string("texture(lut[" + std::to_string((unsigned)sampler / 4) + "], " +
                            lut_index + ")[" + std::to_string((unsigned)sampler & 3) + "]");
     };
@@ -461,8 +464,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
                                 light_src + ".position) + " + light_src + ".dist_atten_bias)";
             index = "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))";
             const unsigned lut_num =
-                ((unsigned)Regs::LightingSampler::DistanceAttenuation + light_config.num);
-            dist_atten = GetLutValue((Regs::LightingSampler)lut_num, index);
+                ((unsigned)LightingRegs::LightingSampler::DistanceAttenuation + light_config.num);
+            dist_atten = GetLutValue((LightingRegs::LightingSampler)lut_num, index);
         }
 
         // If enabled, clamp specular component if lighting result is negative
@@ -472,24 +475,24 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
         // Specular 0 component
         std::string d0_lut_value = "1.0";
         if (lighting.lut_d0.enable &&
-            Pica::Regs::IsLightingSamplerSupported(lighting.config,
-                                                   Pica::Regs::LightingSampler::Distribution0)) {
+            LightingRegs::IsLightingSamplerSupported(
+                lighting.config, LightingRegs::LightingSampler::Distribution0)) {
             // Lookup specular "distribution 0" LUT value
             std::string index =
                 GetLutIndex(light_config.num, lighting.lut_d0.type, lighting.lut_d0.abs_input);
             d0_lut_value = "(" + std::to_string(lighting.lut_d0.scale) + " * " +
-                           GetLutValue(Regs::LightingSampler::Distribution0, index) + ")";
+                           GetLutValue(LightingRegs::LightingSampler::Distribution0, index) + ")";
         }
         std::string specular_0 = "(" + d0_lut_value + " * " + light_src + ".specular_0)";
 
         // If enabled, lookup ReflectRed value, otherwise, 1.0 is used
         if (lighting.lut_rr.enable &&
-            Pica::Regs::IsLightingSamplerSupported(lighting.config,
-                                                   Pica::Regs::LightingSampler::ReflectRed)) {
+            LightingRegs::IsLightingSamplerSupported(lighting.config,
+                                                     LightingRegs::LightingSampler::ReflectRed)) {
             std::string index =
                 GetLutIndex(light_config.num, lighting.lut_rr.type, lighting.lut_rr.abs_input);
             std::string value = "(" + std::to_string(lighting.lut_rr.scale) + " * " +
-                                GetLutValue(Regs::LightingSampler::ReflectRed, index) + ")";
+                                GetLutValue(LightingRegs::LightingSampler::ReflectRed, index) + ")";
             out += "refl_value.r = " + value + ";\n";
         } else {
             out += "refl_value.r = 1.0;\n";
@@ -497,12 +500,13 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
 
         // If enabled, lookup ReflectGreen value, otherwise, ReflectRed value is used
         if (lighting.lut_rg.enable &&
-            Pica::Regs::IsLightingSamplerSupported(lighting.config,
-                                                   Pica::Regs::LightingSampler::ReflectGreen)) {
+            LightingRegs::IsLightingSamplerSupported(lighting.config,
+                                                     LightingRegs::LightingSampler::ReflectGreen)) {
             std::string index =
                 GetLutIndex(light_config.num, lighting.lut_rg.type, lighting.lut_rg.abs_input);
             std::string value = "(" + std::to_string(lighting.lut_rg.scale) + " * " +
-                                GetLutValue(Regs::LightingSampler::ReflectGreen, index) + ")";
+                                GetLutValue(LightingRegs::LightingSampler::ReflectGreen, index) +
+                                ")";
             out += "refl_value.g = " + value + ";\n";
         } else {
             out += "refl_value.g = refl_value.r;\n";
@@ -510,12 +514,13 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
 
         // If enabled, lookup ReflectBlue value, otherwise, ReflectRed value is used
         if (lighting.lut_rb.enable &&
-            Pica::Regs::IsLightingSamplerSupported(lighting.config,
-                                                   Pica::Regs::LightingSampler::ReflectBlue)) {
+            LightingRegs::IsLightingSamplerSupported(lighting.config,
+                                                     LightingRegs::LightingSampler::ReflectBlue)) {
             std::string index =
                 GetLutIndex(light_config.num, lighting.lut_rb.type, lighting.lut_rb.abs_input);
             std::string value = "(" + std::to_string(lighting.lut_rb.scale) + " * " +
-                                GetLutValue(Regs::LightingSampler::ReflectBlue, index) + ")";
+                                GetLutValue(LightingRegs::LightingSampler::ReflectBlue, index) +
+                                ")";
             out += "refl_value.b = " + value + ";\n";
         } else {
             out += "refl_value.b = refl_value.r;\n";
@@ -524,35 +529,39 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
         // Specular 1 component
         std::string d1_lut_value = "1.0";
         if (lighting.lut_d1.enable &&
-            Pica::Regs::IsLightingSamplerSupported(lighting.config,
-                                                   Pica::Regs::LightingSampler::Distribution1)) {
+            LightingRegs::IsLightingSamplerSupported(
+                lighting.config, LightingRegs::LightingSampler::Distribution1)) {
             // Lookup specular "distribution 1" LUT value
             std::string index =
                 GetLutIndex(light_config.num, lighting.lut_d1.type, lighting.lut_d1.abs_input);
             d1_lut_value = "(" + std::to_string(lighting.lut_d1.scale) + " * " +
-                           GetLutValue(Regs::LightingSampler::Distribution1, index) + ")";
+                           GetLutValue(LightingRegs::LightingSampler::Distribution1, index) + ")";
         }
         std::string specular_1 =
             "(" + d1_lut_value + " * refl_value * " + light_src + ".specular_1)";
 
         // Fresnel
-        if (lighting.lut_fr.enable && Pica::Regs::IsLightingSamplerSupported(
-                                          lighting.config, Pica::Regs::LightingSampler::Fresnel)) {
+        if (lighting.lut_fr.enable &&
+            LightingRegs::IsLightingSamplerSupported(lighting.config,
+                                                     LightingRegs::LightingSampler::Fresnel)) {
             // Lookup fresnel LUT value
             std::string index =
                 GetLutIndex(light_config.num, lighting.lut_fr.type, lighting.lut_fr.abs_input);
             std::string value = "(" + std::to_string(lighting.lut_fr.scale) + " * " +
-                                GetLutValue(Regs::LightingSampler::Fresnel, index) + ")";
+                                GetLutValue(LightingRegs::LightingSampler::Fresnel, index) + ")";
 
             // Enabled for difffuse lighting alpha component
-            if (lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::PrimaryAlpha ||
-                lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both)
+            if (lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::PrimaryAlpha ||
+                lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) {
                 out += "diffuse_sum.a  *= " + value + ";\n";
+            }
 
             // Enabled for the specular lighting alpha component
-            if (lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::SecondaryAlpha ||
-                lighting.fresnel_selector == Pica::Regs::LightingFresnelSelector::Both)
+            if (lighting.fresnel_selector ==
+                    LightingRegs::LightingFresnelSelector::SecondaryAlpha ||
+                lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) {
                 out += "specular_sum.a *= " + value + ";\n";
+            }
         }
 
         // Compute primary fragment color (diffuse lighting) function
@@ -633,16 +642,16 @@ vec4 secondary_fragment_color = vec4(0.0);
 )";
 
     // Do not do any sort of processing if it's obvious we're not going to pass the alpha test
-    if (state.alpha_test_func == Regs::CompareFunc::Never) {
+    if (state.alpha_test_func == Pica::FramebufferRegs::CompareFunc::Never) {
         out += "discard; }";
         return out;
     }
 
     // Append the scissor test
-    if (state.scissor_test_mode != Regs::ScissorMode::Disabled) {
+    if (state.scissor_test_mode != RasterizerRegs::ScissorMode::Disabled) {
         out += "if (";
         // Negate the condition if we have to keep only the pixels outside the scissor box
-        if (state.scissor_test_mode == Regs::ScissorMode::Include)
+        if (state.scissor_test_mode == RasterizerRegs::ScissorMode::Include)
             out += "!";
         out += "(gl_FragCoord.x >= scissor_x1 && "
                "gl_FragCoord.y >= scissor_y1 && "
@@ -652,7 +661,7 @@ vec4 secondary_fragment_color = vec4(0.0);
 
     out += "float z_over_w = 1.0 - gl_FragCoord.z * 2.0;\n";
     out += "float depth = z_over_w * depth_scale + depth_offset;\n";
-    if (state.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) {
+    if (state.depthmap_enable == Pica::RasterizerRegs::DepthBuffering::WBuffering) {
         out += "depth /= gl_FragCoord.w;\n";
     }
 
@@ -666,14 +675,14 @@ vec4 secondary_fragment_color = vec4(0.0);
     for (size_t index = 0; index < state.tev_stages.size(); ++index)
         WriteTevStage(out, config, (unsigned)index);
 
-    if (state.alpha_test_func != Regs::CompareFunc::Always) {
+    if (state.alpha_test_func != Pica::FramebufferRegs::CompareFunc::Always) {
         out += "if (";
         AppendAlphaTestCondition(out, state.alpha_test_func);
         out += ") discard;\n";
     }
 
     // Append fog combiner
-    if (state.fog_mode == Regs::FogMode::Fog) {
+    if (state.fog_mode == Pica::TexturingRegs::FogMode::Fog) {
         // Get index into fog LUT
         if (state.fog_flip) {
             out += "float fog_index = (1.0 - depth) * 128.0;\n";
diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h
index cc49867c8a..4b98dafc4a 100644
--- a/src/video_core/renderer_opengl/pica_to_gl.h
+++ b/src/video_core/renderer_opengl/pica_to_gl.h
@@ -12,7 +12,7 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
-#include "video_core/pica.h"
+#include "video_core/regs.h"
 
 using GLvec2 = std::array<GLfloat, 2>;
 using GLvec3 = std::array<GLfloat, 3>;
@@ -20,7 +20,7 @@ using GLvec4 = std::array<GLfloat, 4>;
 
 namespace PicaToGL {
 
-inline GLenum TextureFilterMode(Pica::Regs::TextureConfig::TextureFilter mode) {
+inline GLenum TextureFilterMode(Pica::TexturingRegs::TextureConfig::TextureFilter mode) {
     static const GLenum filter_mode_table[] = {
         GL_NEAREST, // TextureFilter::Nearest
         GL_LINEAR,  // TextureFilter::Linear
@@ -47,7 +47,7 @@ inline GLenum TextureFilterMode(Pica::Regs::TextureConfig::TextureFilter mode) {
     return gl_mode;
 }
 
-inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) {
+inline GLenum WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) {
     static const GLenum wrap_mode_table[] = {
         GL_CLAMP_TO_EDGE,   // WrapMode::ClampToEdge
         GL_CLAMP_TO_BORDER, // WrapMode::ClampToBorder
@@ -76,7 +76,7 @@ inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) {
     return gl_mode;
 }
 
-inline GLenum BlendEquation(Pica::Regs::BlendEquation equation) {
+inline GLenum BlendEquation(Pica::FramebufferRegs::BlendEquation equation) {
     static const GLenum blend_equation_table[] = {
         GL_FUNC_ADD,              // BlendEquation::Add
         GL_FUNC_SUBTRACT,         // BlendEquation::Subtract
@@ -96,7 +96,7 @@ inline GLenum BlendEquation(Pica::Regs::BlendEquation equation) {
     return blend_equation_table[(unsigned)equation];
 }
 
-inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) {
+inline GLenum BlendFunc(Pica::FramebufferRegs::BlendFactor factor) {
     static const GLenum blend_func_table[] = {
         GL_ZERO,                     // BlendFactor::Zero
         GL_ONE,                      // BlendFactor::One
@@ -126,7 +126,7 @@ inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) {
     return blend_func_table[(unsigned)factor];
 }
 
-inline GLenum LogicOp(Pica::Regs::LogicOp op) {
+inline GLenum LogicOp(Pica::FramebufferRegs::LogicOp op) {
     static const GLenum logic_op_table[] = {
         GL_CLEAR,         // Clear
         GL_AND,           // And
@@ -157,7 +157,7 @@ inline GLenum LogicOp(Pica::Regs::LogicOp op) {
     return logic_op_table[(unsigned)op];
 }
 
-inline GLenum CompareFunc(Pica::Regs::CompareFunc func) {
+inline GLenum CompareFunc(Pica::FramebufferRegs::CompareFunc func) {
     static const GLenum compare_func_table[] = {
         GL_NEVER,    // CompareFunc::Never
         GL_ALWAYS,   // CompareFunc::Always
@@ -180,7 +180,7 @@ inline GLenum CompareFunc(Pica::Regs::CompareFunc func) {
     return compare_func_table[(unsigned)func];
 }
 
-inline GLenum StencilOp(Pica::Regs::StencilAction action) {
+inline GLenum StencilOp(Pica::FramebufferRegs::StencilAction action) {
     static const GLenum stencil_op_table[] = {
         GL_KEEP,      // StencilAction::Keep
         GL_ZERO,      // StencilAction::Zero
@@ -210,7 +210,7 @@ inline GLvec4 ColorRGBA8(const u32 color) {
     }};
 }
 
-inline std::array<GLfloat, 3> LightColor(const Pica::Regs::LightColor& color) {
+inline std::array<GLfloat, 3> LightColor(const Pica::LightingRegs::LightColor& color) {
     return {{
         color.r / 255.0f, color.g / 255.0f, color.b / 255.0f,
     }};
diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp
index f5f7ea61da..c860375a1f 100644
--- a/src/video_core/shader/shader.cpp
+++ b/src/video_core/shader/shader.cpp
@@ -7,8 +7,8 @@
 #include "common/bit_set.h"
 #include "common/logging/log.h"
 #include "common/microprofile.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
+#include "video_core/regs.h"
 #include "video_core/shader/shader.h"
 #include "video_core/shader/shader_interpreter.h"
 #ifdef ARCHITECTURE_x86_64
@@ -20,7 +20,7 @@ namespace Pica {
 
 namespace Shader {
 
-OutputVertex OutputVertex::FromAttributeBuffer(const Regs& regs, AttributeBuffer& input) {
+OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& input) {
     // Setup output data
     union {
         OutputVertex ret{};
@@ -33,16 +33,16 @@ OutputVertex OutputVertex::FromAttributeBuffer(const Regs& regs, AttributeBuffer
     for (unsigned int i = 0; i < num_attributes; ++i) {
         const auto& output_register_map = regs.vs_output_attributes[i];
 
-        Regs::VSOutputAttributes::Semantic semantics[4] = {
+        RasterizerRegs::VSOutputAttributes::Semantic semantics[4] = {
             output_register_map.map_x, output_register_map.map_y, output_register_map.map_z,
             output_register_map.map_w};
 
         for (unsigned comp = 0; comp < 4; ++comp) {
-            Regs::VSOutputAttributes::Semantic semantic = semantics[comp];
+            RasterizerRegs::VSOutputAttributes::Semantic semantic = semantics[comp];
             float24* out = &vertex_slots[semantic];
             if (semantic < vertex_slots.size()) {
                 *out = input.attr[i][comp];
-            } else if (semantic != Regs::VSOutputAttributes::INVALID) {
+            } else if (semantic != RasterizerRegs::VSOutputAttributes::INVALID) {
                 LOG_ERROR(HW_GPU, "Invalid/unknown semantic id: %u", (unsigned int)semantic);
             }
         }
@@ -66,7 +66,7 @@ OutputVertex OutputVertex::FromAttributeBuffer(const Regs& regs, AttributeBuffer
     return ret;
 }
 
-void UnitState::LoadInput(const Regs::ShaderConfig& config, const AttributeBuffer& input) {
+void UnitState::LoadInput(const ShaderRegs& config, const AttributeBuffer& input) {
     const unsigned max_attribute = config.max_input_attribute_index;
 
     for (unsigned attr = 0; attr <= max_attribute; ++attr) {
@@ -75,7 +75,7 @@ void UnitState::LoadInput(const Regs::ShaderConfig& config, const AttributeBuffe
     }
 }
 
-void UnitState::WriteOutput(const Regs::ShaderConfig& config, AttributeBuffer& output) {
+void UnitState::WriteOutput(const ShaderRegs& config, AttributeBuffer& output) {
     unsigned int output_i = 0;
     for (unsigned int reg : Common::BitSet<u32>(config.output_mask)) {
         output.attr[output_i++] = registers.output[reg];
diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h
index b188d3edf7..d526824791 100644
--- a/src/video_core/shader/shader.h
+++ b/src/video_core/shader/shader.h
@@ -12,8 +12,8 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/vector_math.h"
-#include "video_core/pica.h"
 #include "video_core/pica_types.h"
+#include "video_core/regs.h"
 
 using nihstro::RegisterType;
 using nihstro::SourceRegister;
@@ -39,19 +39,19 @@ struct OutputVertex {
     INSERT_PADDING_WORDS(1);
     Math::Vec2<float24> tc2;
 
-    static OutputVertex FromAttributeBuffer(const Regs& regs, AttributeBuffer& output);
+    static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& output);
 };
 #define ASSERT_POS(var, pos)                                                                       \
     static_assert(offsetof(OutputVertex, var) == pos * sizeof(float24), "Semantic at wrong "       \
                                                                         "offset.")
-ASSERT_POS(pos, Regs::VSOutputAttributes::POSITION_X);
-ASSERT_POS(quat, Regs::VSOutputAttributes::QUATERNION_X);
-ASSERT_POS(color, Regs::VSOutputAttributes::COLOR_R);
-ASSERT_POS(tc0, Regs::VSOutputAttributes::TEXCOORD0_U);
-ASSERT_POS(tc1, Regs::VSOutputAttributes::TEXCOORD1_U);
-ASSERT_POS(tc0_w, Regs::VSOutputAttributes::TEXCOORD0_W);
-ASSERT_POS(view, Regs::VSOutputAttributes::VIEW_X);
-ASSERT_POS(tc2, Regs::VSOutputAttributes::TEXCOORD2_U);
+ASSERT_POS(pos, RasterizerRegs::VSOutputAttributes::POSITION_X);
+ASSERT_POS(quat, RasterizerRegs::VSOutputAttributes::QUATERNION_X);
+ASSERT_POS(color, RasterizerRegs::VSOutputAttributes::COLOR_R);
+ASSERT_POS(tc0, RasterizerRegs::VSOutputAttributes::TEXCOORD0_U);
+ASSERT_POS(tc1, RasterizerRegs::VSOutputAttributes::TEXCOORD1_U);
+ASSERT_POS(tc0_w, RasterizerRegs::VSOutputAttributes::TEXCOORD0_W);
+ASSERT_POS(view, RasterizerRegs::VSOutputAttributes::VIEW_X);
+ASSERT_POS(tc2, RasterizerRegs::VSOutputAttributes::TEXCOORD2_U);
 #undef ASSERT_POS
 static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD");
 static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has invalid size");
@@ -116,9 +116,9 @@ struct UnitState {
      * @param config Shader configuration registers corresponding to the unit.
      * @param input Attribute buffer to load into the input registers.
      */
-    void LoadInput(const Regs::ShaderConfig& config, const AttributeBuffer& input);
+    void LoadInput(const ShaderRegs& config, const AttributeBuffer& input);
 
-    void WriteOutput(const Regs::ShaderConfig& config, AttributeBuffer& output);
+    void WriteOutput(const ShaderRegs& config, AttributeBuffer& output);
 };
 
 struct ShaderSetup {
diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp
index 81522b8f58..f4d1c46c53 100644
--- a/src/video_core/shader/shader_interpreter.cpp
+++ b/src/video_core/shader/shader_interpreter.cpp
@@ -669,7 +669,7 @@ void InterpreterEngine::Run(const ShaderSetup& setup, UnitState& state) const {
 
 DebugData<true> InterpreterEngine::ProduceDebugInfo(const ShaderSetup& setup,
                                                     const AttributeBuffer& input,
-                                                    const Regs::ShaderConfig& config) const {
+                                                    const ShaderRegs& config) const {
     UnitState state;
     DebugData<true> debug_data;
 
diff --git a/src/video_core/shader/shader_interpreter.h b/src/video_core/shader/shader_interpreter.h
index d7a61e1228..5682b3a392 100644
--- a/src/video_core/shader/shader_interpreter.h
+++ b/src/video_core/shader/shader_interpreter.h
@@ -23,7 +23,7 @@ public:
      * @return Debug information for this shader with regards to the given vertex
      */
     DebugData<true> ProduceDebugInfo(const ShaderSetup& setup, const AttributeBuffer& input,
-                                     const Regs::ShaderConfig& config) const;
+                                     const ShaderRegs& config) const;
 };
 
 } // namespace
diff --git a/src/video_core/texture/texture_decode.cpp b/src/video_core/texture/texture_decode.cpp
index f611a1aa9e..40d3631843 100644
--- a/src/video_core/texture/texture_decode.cpp
+++ b/src/video_core/texture/texture_decode.cpp
@@ -10,12 +10,12 @@
 #include "common/math_util.h"
 #include "common/swap.h"
 #include "common/vector_math.h"
-#include "video_core/pica.h"
+#include "video_core/regs_texturing.h"
 #include "video_core/texture/etc1.h"
 #include "video_core/texture/texture_decode.h"
 #include "video_core/utils.h"
 
-using TextureFormat = Pica::Regs::TextureFormat;
+using TextureFormat = Pica::TexturingRegs::TextureFormat;
 
 namespace Pica {
 namespace Texture {
@@ -82,32 +82,32 @@ Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int
     using VideoCore::MortonInterleave;
 
     switch (info.format) {
-    case Regs::TextureFormat::RGBA8: {
+    case TextureFormat::RGBA8: {
         auto res = Color::DecodeRGBA8(source + MortonInterleave(x, y) * 4);
         return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())};
     }
 
-    case Regs::TextureFormat::RGB8: {
+    case TextureFormat::RGB8: {
         auto res = Color::DecodeRGB8(source + MortonInterleave(x, y) * 3);
         return {res.r(), res.g(), res.b(), 255};
     }
 
-    case Regs::TextureFormat::RGB5A1: {
+    case TextureFormat::RGB5A1: {
         auto res = Color::DecodeRGB5A1(source + MortonInterleave(x, y) * 2);
         return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())};
     }
 
-    case Regs::TextureFormat::RGB565: {
+    case TextureFormat::RGB565: {
         auto res = Color::DecodeRGB565(source + MortonInterleave(x, y) * 2);
         return {res.r(), res.g(), res.b(), 255};
     }
 
-    case Regs::TextureFormat::RGBA4: {
+    case TextureFormat::RGBA4: {
         auto res = Color::DecodeRGBA4(source + MortonInterleave(x, y) * 2);
         return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())};
     }
 
-    case Regs::TextureFormat::IA8: {
+    case TextureFormat::IA8: {
         const u8* source_ptr = source + MortonInterleave(x, y) * 2;
 
         if (disable_alpha) {
@@ -118,17 +118,17 @@ Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int
         }
     }
 
-    case Regs::TextureFormat::RG8: {
+    case TextureFormat::RG8: {
         auto res = Color::DecodeRG8(source + MortonInterleave(x, y) * 2);
         return {res.r(), res.g(), 0, 255};
     }
 
-    case Regs::TextureFormat::I8: {
+    case TextureFormat::I8: {
         const u8* source_ptr = source + MortonInterleave(x, y);
         return {*source_ptr, *source_ptr, *source_ptr, 255};
     }
 
-    case Regs::TextureFormat::A8: {
+    case TextureFormat::A8: {
         const u8* source_ptr = source + MortonInterleave(x, y);
 
         if (disable_alpha) {
@@ -138,7 +138,7 @@ Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int
         }
     }
 
-    case Regs::TextureFormat::IA4: {
+    case TextureFormat::IA4: {
         const u8* source_ptr = source + MortonInterleave(x, y);
 
         u8 i = Color::Convert4To8(((*source_ptr) & 0xF0) >> 4);
@@ -152,7 +152,7 @@ Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int
         }
     }
 
-    case Regs::TextureFormat::I4: {
+    case TextureFormat::I4: {
         u32 morton_offset = MortonInterleave(x, y);
         const u8* source_ptr = source + morton_offset / 2;
 
@@ -162,7 +162,7 @@ Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int
         return {i, i, i, 255};
     }
 
-    case Regs::TextureFormat::A4: {
+    case TextureFormat::A4: {
         u32 morton_offset = MortonInterleave(x, y);
         const u8* source_ptr = source + morton_offset / 2;
 
@@ -176,9 +176,9 @@ Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int
         }
     }
 
-    case Regs::TextureFormat::ETC1:
-    case Regs::TextureFormat::ETC1A4: {
-        bool has_alpha = (info.format == Regs::TextureFormat::ETC1A4);
+    case TextureFormat::ETC1:
+    case TextureFormat::ETC1A4: {
+        bool has_alpha = (info.format == TextureFormat::ETC1A4);
         size_t subtile_size = has_alpha ? 16 : 8;
 
         // ETC1 further subdivides each 8x8 tile into four 4x4 subtiles
@@ -214,8 +214,8 @@ Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int
     }
 }
 
-TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config,
-                                          const Regs::TextureFormat& format) {
+TextureInfo TextureInfo::FromPicaRegister(const TexturingRegs::TextureConfig& config,
+                                          const TexturingRegs::TextureFormat& format) {
     TextureInfo info;
     info.physical_address = config.GetPhysicalAddress();
     info.width = config.width;
diff --git a/src/video_core/texture/texture_decode.h b/src/video_core/texture/texture_decode.h
index 5c636939ae..8507cfeb8f 100644
--- a/src/video_core/texture/texture_decode.h
+++ b/src/video_core/texture/texture_decode.h
@@ -6,27 +6,27 @@
 
 #include "common/common_types.h"
 #include "common/vector_math.h"
-#include "video_core/pica.h"
+#include "video_core/regs_texturing.h"
 
 namespace Pica {
 namespace Texture {
 
 /// Returns the byte size of a 8*8 tile of the specified texture format.
-size_t CalculateTileSize(Pica::Regs::TextureFormat format);
+size_t CalculateTileSize(TexturingRegs::TextureFormat format);
 
 struct TextureInfo {
     PAddr physical_address;
     unsigned int width;
     unsigned int height;
     ptrdiff_t stride;
-    Pica::Regs::TextureFormat format;
+    TexturingRegs::TextureFormat format;
 
-    static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config,
-                                        const Pica::Regs::TextureFormat& format);
+    static TextureInfo FromPicaRegister(const TexturingRegs::TextureConfig& config,
+                                        const TexturingRegs::TextureFormat& format);
 
     /// Calculates stride from format and width, assuming that the entire texture is contiguous.
     void SetDefaultStride() {
-        stride = Pica::Texture::CalculateTileSize(format) * (width / 8);
+        stride = CalculateTileSize(format) * (width / 8);
     }
 };
 
diff --git a/src/video_core/vertex_loader.cpp b/src/video_core/vertex_loader.cpp
index bf83b61ca2..37c5224a91 100644
--- a/src/video_core/vertex_loader.cpp
+++ b/src/video_core/vertex_loader.cpp
@@ -8,15 +8,15 @@
 #include "common/vector_math.h"
 #include "core/memory.h"
 #include "video_core/debug_utils/debug_utils.h"
-#include "video_core/pica.h"
 #include "video_core/pica_state.h"
 #include "video_core/pica_types.h"
+#include "video_core/regs_pipeline.h"
 #include "video_core/shader/shader.h"
 #include "video_core/vertex_loader.h"
 
 namespace Pica {
 
-void VertexLoader::Setup(const Pica::Regs& regs) {
+void VertexLoader::Setup(const PipelineRegs& regs) {
     ASSERT_MSG(!is_setup, "VertexLoader is not intended to be setup more than once.");
 
     const auto& attribute_config = regs.vertex_attributes;
@@ -85,15 +85,16 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex,
                 memory_accesses.AddAccess(
                     source_addr,
                     vertex_attribute_elements[i] *
-                        ((vertex_attribute_formats[i] == Regs::VertexAttributeFormat::FLOAT)
+                        ((vertex_attribute_formats[i] == PipelineRegs::VertexAttributeFormat::FLOAT)
                              ? 4
-                             : (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT)
+                             : (vertex_attribute_formats[i] ==
+                                PipelineRegs::VertexAttributeFormat::SHORT)
                                    ? 2
                                    : 1));
             }
 
             switch (vertex_attribute_formats[i]) {
-            case Regs::VertexAttributeFormat::BYTE: {
+            case PipelineRegs::VertexAttributeFormat::BYTE: {
                 const s8* srcdata =
                     reinterpret_cast<const s8*>(Memory::GetPhysicalPointer(source_addr));
                 for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) {
@@ -101,7 +102,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex,
                 }
                 break;
             }
-            case Regs::VertexAttributeFormat::UBYTE: {
+            case PipelineRegs::VertexAttributeFormat::UBYTE: {
                 const u8* srcdata =
                     reinterpret_cast<const u8*>(Memory::GetPhysicalPointer(source_addr));
                 for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) {
@@ -109,7 +110,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex,
                 }
                 break;
             }
-            case Regs::VertexAttributeFormat::SHORT: {
+            case PipelineRegs::VertexAttributeFormat::SHORT: {
                 const s16* srcdata =
                     reinterpret_cast<const s16*>(Memory::GetPhysicalPointer(source_addr));
                 for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) {
@@ -117,7 +118,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex,
                 }
                 break;
             }
-            case Regs::VertexAttributeFormat::FLOAT: {
+            case PipelineRegs::VertexAttributeFormat::FLOAT: {
                 const float* srcdata =
                     reinterpret_cast<const float*>(Memory::GetPhysicalPointer(source_addr));
                 for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) {
diff --git a/src/video_core/vertex_loader.h b/src/video_core/vertex_loader.h
index 51f3d45b49..02db10aeeb 100644
--- a/src/video_core/vertex_loader.h
+++ b/src/video_core/vertex_loader.h
@@ -2,7 +2,7 @@
 
 #include <array>
 #include "common/common_types.h"
-#include "video_core/pica.h"
+#include "video_core/regs_pipeline.h"
 
 namespace Pica {
 
@@ -17,11 +17,11 @@ struct AttributeBuffer;
 class VertexLoader {
 public:
     VertexLoader() = default;
-    explicit VertexLoader(const Pica::Regs& regs) {
+    explicit VertexLoader(const PipelineRegs& regs) {
         Setup(regs);
     }
 
-    void Setup(const Pica::Regs& regs);
+    void Setup(const PipelineRegs& regs);
     void LoadVertex(u32 base_address, int index, int vertex, Shader::AttributeBuffer& input,
                     DebugUtils::MemoryAccessTracker& memory_accesses);
 
@@ -32,7 +32,7 @@ public:
 private:
     std::array<u32, 16> vertex_attribute_sources;
     std::array<u32, 16> vertex_attribute_strides{};
-    std::array<Regs::VertexAttributeFormat, 16> vertex_attribute_formats;
+    std::array<PipelineRegs::VertexAttributeFormat, 16> vertex_attribute_formats;
     std::array<u32, 16> vertex_attribute_elements{};
     std::array<bool, 16> vertex_attribute_is_default;
     int num_total_attributes = 0;