diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 3b20c7d34..e615b238e 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -151,6 +151,8 @@ add_library(video_core STATIC
 if (ENABLE_VULKAN)
     target_sources(video_core PRIVATE
         renderer_vulkan/declarations.h
+        renderer_vulkan/fixed_pipeline_state.cpp
+        renderer_vulkan/fixed_pipeline_state.h
         renderer_vulkan/maxwell_to_vk.cpp
         renderer_vulkan/maxwell_to_vk.h
         renderer_vulkan/vk_buffer_cache.cpp
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 870b359be..a35e7a195 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -711,13 +711,15 @@ public:
 
                 u32 color_mask_common;
 
-                INSERT_UNION_PADDING_WORDS(0x6);
-
-                u32 rt_separate_frag_data;
+                INSERT_UNION_PADDING_WORDS(0x2);
 
                 f32 depth_bounds[2];
 
-                INSERT_UNION_PADDING_WORDS(0xA);
+                INSERT_UNION_PADDING_WORDS(0x2);
+
+                u32 rt_separate_frag_data;
+
+                INSERT_UNION_PADDING_WORDS(0xC);
 
                 struct {
                     u32 address_high;
@@ -1034,7 +1036,12 @@ public:
                     BitField<4, 1, u32> depth_clamp_far;
                 } view_volume_clip_control;
 
-                INSERT_UNION_PADDING_WORDS(0x21);
+                INSERT_UNION_PADDING_WORDS(0x1F);
+
+                u32 depth_bounds_enable;
+
+                INSERT_UNION_PADDING_WORDS(1);
+
                 struct {
                     u32 enable;
                     LogicOperation operation;
@@ -1444,7 +1451,7 @@ ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D6);
 ASSERT_REG_POSITION(stencil_back_mask, 0x3D7);
 ASSERT_REG_POSITION(color_mask_common, 0x3E4);
 ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB);
-ASSERT_REG_POSITION(depth_bounds, 0x3EC);
+ASSERT_REG_POSITION(depth_bounds, 0x3E7);
 ASSERT_REG_POSITION(zeta, 0x3F8);
 ASSERT_REG_POSITION(clear_flags, 0x43E);
 ASSERT_REG_POSITION(vertex_attrib_format, 0x458);
@@ -1500,6 +1507,7 @@ ASSERT_REG_POSITION(cull, 0x646);
 ASSERT_REG_POSITION(pixel_center_integer, 0x649);
 ASSERT_REG_POSITION(viewport_transform_enabled, 0x64B);
 ASSERT_REG_POSITION(view_volume_clip_control, 0x64F);
+ASSERT_REG_POSITION(depth_bounds_enable, 0x66F);
 ASSERT_REG_POSITION(logic_op, 0x671);
 ASSERT_REG_POSITION(clear_buffers, 0x674);
 ASSERT_REG_POSITION(color_mask, 0x680);
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
new file mode 100644
index 000000000..5a490f6ef
--- /dev/null
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
@@ -0,0 +1,296 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <tuple>
+
+#include <boost/functional/hash.hpp>
+
+#include "common/common_types.h"
+#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
+
+namespace Vulkan {
+
+namespace {
+
+constexpr FixedPipelineState::DepthStencil GetDepthStencilState(const Maxwell& regs) {
+    const FixedPipelineState::StencilFace front_stencil(
+        regs.stencil_front_op_fail, regs.stencil_front_op_zfail, regs.stencil_front_op_zpass,
+        regs.stencil_front_func_func);
+    const FixedPipelineState::StencilFace back_stencil =
+        regs.stencil_two_side_enable
+            ? FixedPipelineState::StencilFace(regs.stencil_back_op_fail, regs.stencil_back_op_zfail,
+                                              regs.stencil_back_op_zpass,
+                                              regs.stencil_back_func_func)
+            : front_stencil;
+    return FixedPipelineState::DepthStencil(
+        regs.depth_test_enable == 1, regs.depth_write_enabled == 1, regs.depth_bounds_enable == 1,
+        regs.stencil_enable == 1, regs.depth_test_func, front_stencil, back_stencil);
+}
+
+constexpr FixedPipelineState::InputAssembly GetInputAssemblyState(const Maxwell& regs) {
+    return FixedPipelineState::InputAssembly(
+        regs.draw.topology, regs.primitive_restart.enabled,
+        regs.draw.topology == Maxwell::PrimitiveTopology::Points ? regs.point_size : 0.0f);
+}
+
+constexpr FixedPipelineState::BlendingAttachment GetBlendingAttachmentState(
+    const Maxwell& regs, std::size_t render_target) {
+    const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : render_target];
+    const std::array components = {mask.R != 0, mask.G != 0, mask.B != 0, mask.A != 0};
+
+    const FixedPipelineState::BlendingAttachment default_blending(
+        false, Maxwell::Blend::Equation::Add, Maxwell::Blend::Factor::One,
+        Maxwell::Blend::Factor::Zero, Maxwell::Blend::Equation::Add, Maxwell::Blend::Factor::One,
+        Maxwell::Blend::Factor::Zero, components);
+    if (render_target >= regs.rt_control.count) {
+        return default_blending;
+    }
+
+    if (!regs.independent_blend_enable) {
+        const auto& src = regs.blend;
+        if (!src.enable[render_target]) {
+            return default_blending;
+        }
+        return FixedPipelineState::BlendingAttachment(
+            true, src.equation_rgb, src.factor_source_rgb, src.factor_dest_rgb, src.equation_a,
+            src.factor_source_a, src.factor_dest_a, components);
+    }
+
+    if (!regs.blend.enable[render_target]) {
+        return default_blending;
+    }
+    const auto& src = regs.independent_blend[render_target];
+    return FixedPipelineState::BlendingAttachment(
+        true, src.equation_rgb, src.factor_source_rgb, src.factor_dest_rgb, src.equation_a,
+        src.factor_source_a, src.factor_dest_a, components);
+}
+
+constexpr FixedPipelineState::ColorBlending GetColorBlendingState(const Maxwell& regs) {
+    return FixedPipelineState::ColorBlending(
+        {regs.blend_color.r, regs.blend_color.g, regs.blend_color.b, regs.blend_color.a},
+        regs.rt_control.count,
+        {GetBlendingAttachmentState(regs, 0), GetBlendingAttachmentState(regs, 1),
+         GetBlendingAttachmentState(regs, 2), GetBlendingAttachmentState(regs, 3),
+         GetBlendingAttachmentState(regs, 4), GetBlendingAttachmentState(regs, 5),
+         GetBlendingAttachmentState(regs, 6), GetBlendingAttachmentState(regs, 7)});
+}
+
+constexpr FixedPipelineState::Tessellation GetTessellationState(const Maxwell& regs) {
+    return FixedPipelineState::Tessellation(regs.patch_vertices, regs.tess_mode.prim,
+                                            regs.tess_mode.spacing, regs.tess_mode.cw != 0);
+}
+
+constexpr std::size_t Point = 0;
+constexpr std::size_t Line = 1;
+constexpr std::size_t Polygon = 2;
+constexpr std::array PolygonOffsetEnableLUT = {
+    Point,   // Points
+    Line,    // Lines
+    Line,    // LineLoop
+    Line,    // LineStrip
+    Polygon, // Triangles
+    Polygon, // TriangleStrip
+    Polygon, // TriangleFan
+    Polygon, // Quads
+    Polygon, // QuadStrip
+    Polygon, // Polygon
+    Line,    // LinesAdjacency
+    Line,    // LineStripAdjacency
+    Polygon, // TrianglesAdjacency
+    Polygon, // TriangleStripAdjacency
+    Polygon, // Patches
+};
+
+constexpr FixedPipelineState::Rasterizer GetRasterizerState(const Maxwell& regs) {
+    const std::array enabled_lut = {regs.polygon_offset_point_enable,
+                                    regs.polygon_offset_line_enable,
+                                    regs.polygon_offset_fill_enable};
+    const auto topology = static_cast<std::size_t>(regs.draw.topology.Value());
+    const bool depth_bias_enabled = enabled_lut[PolygonOffsetEnableLUT[topology]];
+
+    Maxwell::Cull::FrontFace front_face = regs.cull.front_face;
+    if (regs.screen_y_control.triangle_rast_flip != 0 &&
+        regs.viewport_transform[0].scale_y > 0.0f) {
+        if (front_face == Maxwell::Cull::FrontFace::CounterClockWise)
+            front_face = Maxwell::Cull::FrontFace::ClockWise;
+        else if (front_face == Maxwell::Cull::FrontFace::ClockWise)
+            front_face = Maxwell::Cull::FrontFace::CounterClockWise;
+    }
+
+    const bool gl_ndc = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne;
+    return FixedPipelineState::Rasterizer(regs.cull.enabled, depth_bias_enabled, gl_ndc,
+                                          regs.cull.cull_face, front_face);
+}
+
+} // Anonymous namespace
+
+std::size_t FixedPipelineState::VertexBinding::Hash() const noexcept {
+    return (index << stride) ^ divisor;
+}
+
+bool FixedPipelineState::VertexBinding::operator==(const VertexBinding& rhs) const noexcept {
+    return std::tie(index, stride, divisor) == std::tie(rhs.index, rhs.stride, rhs.divisor);
+}
+
+std::size_t FixedPipelineState::VertexAttribute::Hash() const noexcept {
+    return static_cast<std::size_t>(index) ^ (static_cast<std::size_t>(buffer) << 13) ^
+           (static_cast<std::size_t>(type) << 22) ^ (static_cast<std::size_t>(size) << 31) ^
+           (static_cast<std::size_t>(offset) << 36);
+}
+
+bool FixedPipelineState::VertexAttribute::operator==(const VertexAttribute& rhs) const noexcept {
+    return std::tie(index, buffer, type, size, offset) ==
+           std::tie(rhs.index, rhs.buffer, rhs.type, rhs.size, rhs.offset);
+}
+
+std::size_t FixedPipelineState::StencilFace::Hash() const noexcept {
+    return static_cast<std::size_t>(action_stencil_fail) ^
+           (static_cast<std::size_t>(action_depth_fail) << 4) ^
+           (static_cast<std::size_t>(action_depth_fail) << 20) ^
+           (static_cast<std::size_t>(action_depth_pass) << 36);
+}
+
+bool FixedPipelineState::StencilFace::operator==(const StencilFace& rhs) const noexcept {
+    return std::tie(action_stencil_fail, action_depth_fail, action_depth_pass, test_func) ==
+           std::tie(rhs.action_stencil_fail, rhs.action_depth_fail, rhs.action_depth_pass,
+                    rhs.test_func);
+}
+
+std::size_t FixedPipelineState::BlendingAttachment::Hash() const noexcept {
+    return static_cast<std::size_t>(enable) ^ (static_cast<std::size_t>(rgb_equation) << 5) ^
+           (static_cast<std::size_t>(src_rgb_func) << 10) ^
+           (static_cast<std::size_t>(dst_rgb_func) << 15) ^
+           (static_cast<std::size_t>(a_equation) << 20) ^
+           (static_cast<std::size_t>(src_a_func) << 25) ^
+           (static_cast<std::size_t>(dst_a_func) << 30) ^
+           (static_cast<std::size_t>(components[0]) << 35) ^
+           (static_cast<std::size_t>(components[1]) << 36) ^
+           (static_cast<std::size_t>(components[2]) << 37) ^
+           (static_cast<std::size_t>(components[3]) << 38);
+}
+
+bool FixedPipelineState::BlendingAttachment::operator==(const BlendingAttachment& rhs) const
+    noexcept {
+    return std::tie(enable, rgb_equation, src_rgb_func, dst_rgb_func, a_equation, src_a_func,
+                    dst_a_func, components) ==
+           std::tie(rhs.enable, rhs.rgb_equation, rhs.src_rgb_func, rhs.dst_rgb_func,
+                    rhs.a_equation, rhs.src_a_func, rhs.dst_a_func, rhs.components);
+}
+
+std::size_t FixedPipelineState::VertexInput::Hash() const noexcept {
+    std::size_t hash = num_bindings ^ (num_attributes << 32);
+    for (std::size_t i = 0; i < num_bindings; ++i) {
+        boost::hash_combine(hash, bindings[i].Hash());
+    }
+    for (std::size_t i = 0; i < num_attributes; ++i) {
+        boost::hash_combine(hash, attributes[i].Hash());
+    }
+    return hash;
+}
+
+bool FixedPipelineState::VertexInput::operator==(const VertexInput& rhs) const noexcept {
+    return std::equal(bindings.begin(), bindings.begin() + num_bindings, rhs.bindings.begin(),
+                      rhs.bindings.begin() + rhs.num_bindings) &&
+           std::equal(attributes.begin(), attributes.begin() + num_attributes,
+                      rhs.attributes.begin(), rhs.attributes.begin() + rhs.num_attributes);
+}
+
+std::size_t FixedPipelineState::InputAssembly::Hash() const noexcept {
+    std::size_t point_size_int = 0;
+    std::memcpy(&point_size_int, &point_size, sizeof(point_size));
+    return (static_cast<std::size_t>(topology) << 24) ^ (point_size_int << 32) ^
+           static_cast<std::size_t>(primitive_restart_enable);
+}
+
+bool FixedPipelineState::InputAssembly::operator==(const InputAssembly& rhs) const noexcept {
+    return std::tie(topology, primitive_restart_enable, point_size) ==
+           std::tie(rhs.topology, rhs.primitive_restart_enable, rhs.point_size);
+}
+
+std::size_t FixedPipelineState::Tessellation::Hash() const noexcept {
+    return static_cast<std::size_t>(patch_control_points) ^
+           (static_cast<std::size_t>(primitive) << 6) ^ (static_cast<std::size_t>(spacing) << 8) ^
+           (static_cast<std::size_t>(clockwise) << 10);
+}
+
+bool FixedPipelineState::Tessellation::operator==(const Tessellation& rhs) const noexcept {
+    return std::tie(patch_control_points, primitive, spacing, clockwise) ==
+           std::tie(rhs.patch_control_points, rhs.primitive, rhs.spacing, rhs.clockwise);
+}
+
+std::size_t FixedPipelineState::Rasterizer::Hash() const noexcept {
+    return static_cast<std::size_t>(cull_enable) ^
+           (static_cast<std::size_t>(depth_bias_enable) << 1) ^
+           (static_cast<std::size_t>(ndc_minus_one_to_one) << 2) ^
+           (static_cast<std::size_t>(cull_face) << 24) ^
+           (static_cast<std::size_t>(front_face) << 48);
+}
+
+bool FixedPipelineState::Rasterizer::operator==(const Rasterizer& rhs) const noexcept {
+    return std::tie(cull_enable, depth_bias_enable, ndc_minus_one_to_one, cull_face, front_face) ==
+           std::tie(rhs.cull_enable, rhs.depth_bias_enable, rhs.ndc_minus_one_to_one, rhs.cull_face,
+                    rhs.front_face);
+}
+
+std::size_t FixedPipelineState::DepthStencil::Hash() const noexcept {
+    std::size_t hash = static_cast<std::size_t>(depth_test_enable) ^
+                       (static_cast<std::size_t>(depth_write_enable) << 1) ^
+                       (static_cast<std::size_t>(depth_bounds_enable) << 2) ^
+                       (static_cast<std::size_t>(stencil_enable) << 3) ^
+                       (static_cast<std::size_t>(depth_test_function) << 4);
+    boost::hash_combine(hash, front_stencil.Hash());
+    boost::hash_combine(hash, back_stencil.Hash());
+    return hash;
+}
+
+bool FixedPipelineState::DepthStencil::operator==(const DepthStencil& rhs) const noexcept {
+    return std::tie(depth_test_enable, depth_write_enable, depth_bounds_enable, depth_test_function,
+                    stencil_enable, front_stencil, back_stencil) ==
+           std::tie(rhs.depth_test_enable, rhs.depth_write_enable, rhs.depth_bounds_enable,
+                    rhs.depth_test_function, rhs.stencil_enable, rhs.front_stencil,
+                    rhs.back_stencil);
+}
+
+std::size_t FixedPipelineState::ColorBlending::Hash() const noexcept {
+    std::size_t hash = attachments_count << 13;
+    for (std::size_t rt = 0; rt < static_cast<std::size_t>(attachments_count); ++rt) {
+        boost::hash_combine(hash, attachments[rt].Hash());
+    }
+    return hash;
+}
+
+bool FixedPipelineState::ColorBlending::operator==(const ColorBlending& rhs) const noexcept {
+    return std::equal(attachments.begin(), attachments.begin() + attachments_count,
+                      rhs.attachments.begin(), rhs.attachments.begin() + rhs.attachments_count);
+}
+
+std::size_t FixedPipelineState::Hash() const noexcept {
+    std::size_t hash = 0;
+    boost::hash_combine(hash, vertex_input.Hash());
+    boost::hash_combine(hash, input_assembly.Hash());
+    boost::hash_combine(hash, tessellation.Hash());
+    boost::hash_combine(hash, rasterizer.Hash());
+    boost::hash_combine(hash, depth_stencil.Hash());
+    boost::hash_combine(hash, color_blending.Hash());
+    return hash;
+}
+
+bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept {
+    return std::tie(vertex_input, input_assembly, tessellation, rasterizer, depth_stencil,
+                    color_blending) == std::tie(rhs.vertex_input, rhs.input_assembly,
+                                                rhs.tessellation, rhs.rasterizer, rhs.depth_stencil,
+                                                rhs.color_blending);
+}
+
+FixedPipelineState GetFixedPipelineState(const Maxwell& regs) {
+    FixedPipelineState fixed_state;
+    fixed_state.input_assembly = GetInputAssemblyState(regs);
+    fixed_state.tessellation = GetTessellationState(regs);
+    fixed_state.rasterizer = GetRasterizerState(regs);
+    fixed_state.depth_stencil = GetDepthStencilState(regs);
+    fixed_state.color_blending = GetColorBlendingState(regs);
+    return fixed_state;
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
new file mode 100644
index 000000000..04152c0d4
--- /dev/null
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
@@ -0,0 +1,282 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <type_traits>
+
+#include "common/common_types.h"
+
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/surface.h"
+
+namespace Vulkan {
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+
+// TODO(Rodrigo): Optimize this structure.
+
+struct FixedPipelineState {
+    using PixelFormat = VideoCore::Surface::PixelFormat;
+
+    struct VertexBinding {
+        constexpr VertexBinding(u32 index, u32 stride, u32 divisor)
+            : index{index}, stride{stride}, divisor{divisor} {}
+        VertexBinding() = default;
+
+        u32 index;
+        u32 stride;
+        u32 divisor;
+
+        std::size_t Hash() const noexcept;
+
+        bool operator==(const VertexBinding& rhs) const noexcept;
+
+        bool operator!=(const VertexBinding& rhs) const noexcept {
+            return !operator==(rhs);
+        }
+    };
+
+    struct VertexAttribute {
+        constexpr VertexAttribute(u32 index, u32 buffer, Maxwell::VertexAttribute::Type type,
+                                  Maxwell::VertexAttribute::Size size, u32 offset)
+            : index{index}, buffer{buffer}, type{type}, size{size}, offset{offset} {}
+        VertexAttribute() = default;
+
+        u32 index;
+        u32 buffer;
+        Maxwell::VertexAttribute::Type type;
+        Maxwell::VertexAttribute::Size size;
+        u32 offset;
+
+        std::size_t Hash() const noexcept;
+
+        bool operator==(const VertexAttribute& rhs) const noexcept;
+
+        bool operator!=(const VertexAttribute& rhs) const noexcept {
+            return !operator==(rhs);
+        }
+    };
+
+    struct StencilFace {
+        constexpr StencilFace(Maxwell::StencilOp action_stencil_fail,
+                              Maxwell::StencilOp action_depth_fail,
+                              Maxwell::StencilOp action_depth_pass, Maxwell::ComparisonOp test_func)
+            : action_stencil_fail{action_stencil_fail}, action_depth_fail{action_depth_fail},
+              action_depth_pass{action_depth_pass}, test_func{test_func} {}
+        StencilFace() = default;
+
+        Maxwell::StencilOp action_stencil_fail;
+        Maxwell::StencilOp action_depth_fail;
+        Maxwell::StencilOp action_depth_pass;
+        Maxwell::ComparisonOp test_func;
+
+        std::size_t Hash() const noexcept;
+
+        bool operator==(const StencilFace& rhs) const noexcept;
+
+        bool operator!=(const StencilFace& rhs) const noexcept {
+            return !operator==(rhs);
+        }
+    };
+
+    struct BlendingAttachment {
+        constexpr BlendingAttachment(bool enable, Maxwell::Blend::Equation rgb_equation,
+                                     Maxwell::Blend::Factor src_rgb_func,
+                                     Maxwell::Blend::Factor dst_rgb_func,
+                                     Maxwell::Blend::Equation a_equation,
+                                     Maxwell::Blend::Factor src_a_func,
+                                     Maxwell::Blend::Factor dst_a_func,
+                                     std::array<bool, 4> components)
+            : enable{enable}, rgb_equation{rgb_equation}, src_rgb_func{src_rgb_func},
+              dst_rgb_func{dst_rgb_func}, a_equation{a_equation}, src_a_func{src_a_func},
+              dst_a_func{dst_a_func}, components{components} {}
+        BlendingAttachment() = default;
+
+        bool enable;
+        Maxwell::Blend::Equation rgb_equation;
+        Maxwell::Blend::Factor src_rgb_func;
+        Maxwell::Blend::Factor dst_rgb_func;
+        Maxwell::Blend::Equation a_equation;
+        Maxwell::Blend::Factor src_a_func;
+        Maxwell::Blend::Factor dst_a_func;
+        std::array<bool, 4> components;
+
+        std::size_t Hash() const noexcept;
+
+        bool operator==(const BlendingAttachment& rhs) const noexcept;
+
+        bool operator!=(const BlendingAttachment& rhs) const noexcept {
+            return !operator==(rhs);
+        }
+    };
+
+    struct VertexInput {
+        std::size_t num_bindings = 0;
+        std::size_t num_attributes = 0;
+        std::array<VertexBinding, Maxwell::NumVertexArrays> bindings;
+        std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes;
+
+        std::size_t Hash() const noexcept;
+
+        bool operator==(const VertexInput& rhs) const noexcept;
+
+        bool operator!=(const VertexInput& rhs) const noexcept {
+            return !operator==(rhs);
+        }
+    };
+
+    struct InputAssembly {
+        constexpr InputAssembly(Maxwell::PrimitiveTopology topology, bool primitive_restart_enable,
+                                float point_size)
+            : topology{topology}, primitive_restart_enable{primitive_restart_enable},
+              point_size{point_size} {}
+        InputAssembly() = default;
+
+        Maxwell::PrimitiveTopology topology;
+        bool primitive_restart_enable;
+        float point_size;
+
+        std::size_t Hash() const noexcept;
+
+        bool operator==(const InputAssembly& rhs) const noexcept;
+
+        bool operator!=(const InputAssembly& rhs) const noexcept {
+            return !operator==(rhs);
+        }
+    };
+
+    struct Tessellation {
+        constexpr Tessellation(u32 patch_control_points, Maxwell::TessellationPrimitive primitive,
+                               Maxwell::TessellationSpacing spacing, bool clockwise)
+            : patch_control_points{patch_control_points}, primitive{primitive}, spacing{spacing},
+              clockwise{clockwise} {}
+        Tessellation() = default;
+
+        u32 patch_control_points;
+        Maxwell::TessellationPrimitive primitive;
+        Maxwell::TessellationSpacing spacing;
+        bool clockwise;
+
+        std::size_t Hash() const noexcept;
+
+        bool operator==(const Tessellation& rhs) const noexcept;
+
+        bool operator!=(const Tessellation& rhs) const noexcept {
+            return !operator==(rhs);
+        }
+    };
+
+    struct Rasterizer {
+        constexpr Rasterizer(bool cull_enable, bool depth_bias_enable, bool ndc_minus_one_to_one,
+                             Maxwell::Cull::CullFace cull_face, Maxwell::Cull::FrontFace front_face)
+            : cull_enable{cull_enable}, depth_bias_enable{depth_bias_enable},
+              ndc_minus_one_to_one{ndc_minus_one_to_one}, cull_face{cull_face}, front_face{
+                                                                                    front_face} {}
+        Rasterizer() = default;
+
+        bool cull_enable;
+        bool depth_bias_enable;
+        bool ndc_minus_one_to_one;
+        Maxwell::Cull::CullFace cull_face;
+        Maxwell::Cull::FrontFace front_face;
+
+        std::size_t Hash() const noexcept;
+
+        bool operator==(const Rasterizer& rhs) const noexcept;
+
+        bool operator!=(const Rasterizer& rhs) const noexcept {
+            return !operator==(rhs);
+        }
+    };
+
+    struct DepthStencil {
+        constexpr DepthStencil(bool depth_test_enable, bool depth_write_enable,
+                               bool depth_bounds_enable, bool stencil_enable,
+                               Maxwell::ComparisonOp depth_test_function, StencilFace front_stencil,
+                               StencilFace back_stencil)
+            : depth_test_enable{depth_test_enable}, depth_write_enable{depth_write_enable},
+              depth_bounds_enable{depth_bounds_enable}, stencil_enable{stencil_enable},
+              depth_test_function{depth_test_function}, front_stencil{front_stencil},
+              back_stencil{back_stencil} {}
+        DepthStencil() = default;
+
+        bool depth_test_enable;
+        bool depth_write_enable;
+        bool depth_bounds_enable;
+        bool stencil_enable;
+        Maxwell::ComparisonOp depth_test_function;
+        StencilFace front_stencil;
+        StencilFace back_stencil;
+
+        std::size_t Hash() const noexcept;
+
+        bool operator==(const DepthStencil& rhs) const noexcept;
+
+        bool operator!=(const DepthStencil& rhs) const noexcept {
+            return !operator==(rhs);
+        }
+    };
+
+    struct ColorBlending {
+        constexpr ColorBlending(
+            std::array<float, 4> blend_constants, std::size_t attachments_count,
+            std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments)
+            : attachments_count{attachments_count}, attachments{attachments} {}
+        ColorBlending() = default;
+
+        std::size_t attachments_count;
+        std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
+
+        std::size_t Hash() const noexcept;
+
+        bool operator==(const ColorBlending& rhs) const noexcept;
+
+        bool operator!=(const ColorBlending& rhs) const noexcept {
+            return !operator==(rhs);
+        }
+    };
+
+    std::size_t Hash() const noexcept;
+
+    bool operator==(const FixedPipelineState& rhs) const noexcept;
+
+    bool operator!=(const FixedPipelineState& rhs) const noexcept {
+        return !operator==(rhs);
+    }
+
+    VertexInput vertex_input;
+    InputAssembly input_assembly;
+    Tessellation tessellation;
+    Rasterizer rasterizer;
+    DepthStencil depth_stencil;
+    ColorBlending color_blending;
+};
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexBinding>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexAttribute>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::StencilFace>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::BlendingAttachment>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexInput>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::InputAssembly>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::Tessellation>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::Rasterizer>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::DepthStencil>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState::ColorBlending>);
+static_assert(std::is_trivially_copyable_v<FixedPipelineState>);
+
+FixedPipelineState GetFixedPipelineState(const Maxwell& regs);
+
+} // namespace Vulkan
+
+namespace std {
+
+template <>
+struct hash<Vulkan::FixedPipelineState> {
+    std::size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept {
+        return k.Hash();
+    }
+};
+
+} // namespace std