diff options
3 files changed, 140 insertions, 97 deletions
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index 97aab951a..8734045e5 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp | |||
| @@ -12,23 +12,32 @@ | |||
| 12 | 12 | ||
| 13 | namespace Vulkan { | 13 | namespace Vulkan { |
| 14 | 14 | ||
| 15 | namespace { | 15 | void FixedPipelineState::DepthStencil::Fill(const Maxwell& regs) noexcept { |
| 16 | 16 | raw = 0; | |
| 17 | constexpr FixedPipelineState::DepthStencil GetDepthStencilState(const Maxwell& regs) { | 17 | front.action_stencil_fail.Assign(PackStencilOp(regs.stencil_front_op_fail)); |
| 18 | const FixedPipelineState::StencilFace front_stencil( | 18 | front.action_depth_fail.Assign(PackStencilOp(regs.stencil_front_op_zfail)); |
| 19 | regs.stencil_front_op_fail, regs.stencil_front_op_zfail, regs.stencil_front_op_zpass, | 19 | front.action_depth_pass.Assign(PackStencilOp(regs.stencil_front_op_zpass)); |
| 20 | regs.stencil_front_func_func); | 20 | front.test_func.Assign(PackComparisonOp(regs.stencil_front_func_func)); |
| 21 | const FixedPipelineState::StencilFace back_stencil = | 21 | if (regs.stencil_two_side_enable) { |
| 22 | regs.stencil_two_side_enable | 22 | back.action_stencil_fail.Assign(PackStencilOp(regs.stencil_back_op_fail)); |
| 23 | ? FixedPipelineState::StencilFace(regs.stencil_back_op_fail, regs.stencil_back_op_zfail, | 23 | back.action_depth_fail.Assign(PackStencilOp(regs.stencil_back_op_zfail)); |
| 24 | regs.stencil_back_op_zpass, | 24 | back.action_depth_pass.Assign(PackStencilOp(regs.stencil_back_op_zpass)); |
| 25 | regs.stencil_back_func_func) | 25 | back.test_func.Assign(PackComparisonOp(regs.stencil_back_func_func)); |
| 26 | : front_stencil; | 26 | } else { |
| 27 | return FixedPipelineState::DepthStencil( | 27 | back.action_stencil_fail.Assign(front.action_stencil_fail); |
| 28 | regs.depth_test_enable == 1, regs.depth_write_enabled == 1, regs.depth_bounds_enable == 1, | 28 | back.action_depth_fail.Assign(front.action_depth_fail); |
| 29 | regs.stencil_enable == 1, regs.depth_test_func, front_stencil, back_stencil); | 29 | back.action_depth_pass.Assign(front.action_depth_pass); |
| 30 | back.test_func.Assign(front.test_func); | ||
| 31 | } | ||
| 32 | depth_test_enable.Assign(regs.depth_test_enable); | ||
| 33 | depth_write_enable.Assign(regs.depth_write_enabled); | ||
| 34 | depth_bounds_enable.Assign(regs.depth_bounds_enable); | ||
| 35 | stencil_enable.Assign(regs.stencil_enable); | ||
| 36 | depth_test_func.Assign(PackComparisonOp(regs.depth_test_func)); | ||
| 30 | } | 37 | } |
| 31 | 38 | ||
| 39 | namespace { | ||
| 40 | |||
| 32 | constexpr FixedPipelineState::InputAssembly GetInputAssemblyState(const Maxwell& regs) { | 41 | constexpr FixedPipelineState::InputAssembly GetInputAssemblyState(const Maxwell& regs) { |
| 33 | return FixedPipelineState::InputAssembly( | 42 | return FixedPipelineState::InputAssembly( |
| 34 | regs.draw.topology, regs.primitive_restart.enabled, | 43 | regs.draw.topology, regs.primitive_restart.enabled, |
| @@ -129,19 +138,6 @@ constexpr FixedPipelineState::Rasterizer GetRasterizerState(const Maxwell& regs) | |||
| 129 | 138 | ||
| 130 | } // Anonymous namespace | 139 | } // Anonymous namespace |
| 131 | 140 | ||
| 132 | std::size_t FixedPipelineState::StencilFace::Hash() const noexcept { | ||
| 133 | return static_cast<std::size_t>(action_stencil_fail) ^ | ||
| 134 | (static_cast<std::size_t>(action_depth_fail) << 4) ^ | ||
| 135 | (static_cast<std::size_t>(action_depth_fail) << 20) ^ | ||
| 136 | (static_cast<std::size_t>(action_depth_pass) << 36); | ||
| 137 | } | ||
| 138 | |||
| 139 | bool FixedPipelineState::StencilFace::operator==(const StencilFace& rhs) const noexcept { | ||
| 140 | return std::tie(action_stencil_fail, action_depth_fail, action_depth_pass, test_func) == | ||
| 141 | std::tie(rhs.action_stencil_fail, rhs.action_depth_fail, rhs.action_depth_pass, | ||
| 142 | rhs.test_func); | ||
| 143 | } | ||
| 144 | |||
| 145 | std::size_t FixedPipelineState::BlendingAttachment::Hash() const noexcept { | 141 | std::size_t FixedPipelineState::BlendingAttachment::Hash() const noexcept { |
| 146 | return static_cast<std::size_t>(enable) ^ (static_cast<std::size_t>(rgb_equation) << 5) ^ | 142 | return static_cast<std::size_t>(enable) ^ (static_cast<std::size_t>(rgb_equation) << 5) ^ |
| 147 | (static_cast<std::size_t>(src_rgb_func) << 10) ^ | 143 | (static_cast<std::size_t>(src_rgb_func) << 10) ^ |
| @@ -212,22 +208,11 @@ bool FixedPipelineState::Rasterizer::operator==(const Rasterizer& rhs) const noe | |||
| 212 | } | 208 | } |
| 213 | 209 | ||
| 214 | std::size_t FixedPipelineState::DepthStencil::Hash() const noexcept { | 210 | std::size_t FixedPipelineState::DepthStencil::Hash() const noexcept { |
| 215 | std::size_t hash = static_cast<std::size_t>(depth_test_enable) ^ | 211 | return raw; |
| 216 | (static_cast<std::size_t>(depth_write_enable) << 1) ^ | ||
| 217 | (static_cast<std::size_t>(depth_bounds_enable) << 2) ^ | ||
| 218 | (static_cast<std::size_t>(stencil_enable) << 3) ^ | ||
| 219 | (static_cast<std::size_t>(depth_test_function) << 4); | ||
| 220 | boost::hash_combine(hash, front_stencil.Hash()); | ||
| 221 | boost::hash_combine(hash, back_stencil.Hash()); | ||
| 222 | return hash; | ||
| 223 | } | 212 | } |
| 224 | 213 | ||
| 225 | bool FixedPipelineState::DepthStencil::operator==(const DepthStencil& rhs) const noexcept { | 214 | bool FixedPipelineState::DepthStencil::operator==(const DepthStencil& rhs) const noexcept { |
| 226 | return std::tie(depth_test_enable, depth_write_enable, depth_bounds_enable, depth_test_function, | 215 | return raw == rhs.raw; |
| 227 | stencil_enable, front_stencil, back_stencil) == | ||
| 228 | std::tie(rhs.depth_test_enable, rhs.depth_write_enable, rhs.depth_bounds_enable, | ||
| 229 | rhs.depth_test_function, rhs.stencil_enable, rhs.front_stencil, | ||
| 230 | rhs.back_stencil); | ||
| 231 | } | 216 | } |
| 232 | 217 | ||
| 233 | std::size_t FixedPipelineState::ColorBlending::Hash() const noexcept { | 218 | std::size_t FixedPipelineState::ColorBlending::Hash() const noexcept { |
| @@ -266,9 +251,60 @@ FixedPipelineState GetFixedPipelineState(const Maxwell& regs) { | |||
| 266 | fixed_state.input_assembly = GetInputAssemblyState(regs); | 251 | fixed_state.input_assembly = GetInputAssemblyState(regs); |
| 267 | fixed_state.tessellation = GetTessellationState(regs); | 252 | fixed_state.tessellation = GetTessellationState(regs); |
| 268 | fixed_state.rasterizer = GetRasterizerState(regs); | 253 | fixed_state.rasterizer = GetRasterizerState(regs); |
| 269 | fixed_state.depth_stencil = GetDepthStencilState(regs); | 254 | fixed_state.depth_stencil.Fill(regs); |
| 270 | fixed_state.color_blending = GetColorBlendingState(regs); | 255 | fixed_state.color_blending = GetColorBlendingState(regs); |
| 271 | return fixed_state; | 256 | return fixed_state; |
| 272 | } | 257 | } |
| 273 | 258 | ||
| 259 | u32 FixedPipelineState::PackComparisonOp(Maxwell::ComparisonOp op) noexcept { | ||
| 260 | // OpenGL enums go from 0x200 to 0x207 and the others from 1 to 8 | ||
| 261 | // If we substract 0x200 to OpenGL enums and 1 to the others we get a 0-7 range. | ||
| 262 | // Perfect for a hash. | ||
| 263 | const u32 value = static_cast<u32>(op); | ||
| 264 | return value - (value >= 0x200 ? 0x200 : 1); | ||
| 265 | } | ||
| 266 | |||
| 267 | Maxwell::ComparisonOp FixedPipelineState::UnpackComparisonOp(u32 packed) noexcept { | ||
| 268 | // Read PackComparisonOp for the logic behind this. | ||
| 269 | return static_cast<Maxwell::ComparisonOp>(packed + 1); | ||
| 270 | } | ||
| 271 | |||
| 272 | u32 FixedPipelineState::PackStencilOp(Maxwell::StencilOp op) noexcept { | ||
| 273 | switch (op) { | ||
| 274 | case Maxwell::StencilOp::Keep: | ||
| 275 | case Maxwell::StencilOp::KeepOGL: | ||
| 276 | return 0; | ||
| 277 | case Maxwell::StencilOp::Zero: | ||
| 278 | case Maxwell::StencilOp::ZeroOGL: | ||
| 279 | return 1; | ||
| 280 | case Maxwell::StencilOp::Replace: | ||
| 281 | case Maxwell::StencilOp::ReplaceOGL: | ||
| 282 | return 2; | ||
| 283 | case Maxwell::StencilOp::Incr: | ||
| 284 | case Maxwell::StencilOp::IncrOGL: | ||
| 285 | return 3; | ||
| 286 | case Maxwell::StencilOp::Decr: | ||
| 287 | case Maxwell::StencilOp::DecrOGL: | ||
| 288 | return 4; | ||
| 289 | case Maxwell::StencilOp::Invert: | ||
| 290 | case Maxwell::StencilOp::InvertOGL: | ||
| 291 | return 5; | ||
| 292 | case Maxwell::StencilOp::IncrWrap: | ||
| 293 | case Maxwell::StencilOp::IncrWrapOGL: | ||
| 294 | return 6; | ||
| 295 | case Maxwell::StencilOp::DecrWrap: | ||
| 296 | case Maxwell::StencilOp::DecrWrapOGL: | ||
| 297 | return 7; | ||
| 298 | } | ||
| 299 | return 0; | ||
| 300 | } | ||
| 301 | |||
| 302 | Maxwell::StencilOp FixedPipelineState::UnpackStencilOp(u32 packed) noexcept { | ||
| 303 | static constexpr std::array LUT = {Maxwell::StencilOp::Keep, Maxwell::StencilOp::Zero, | ||
| 304 | Maxwell::StencilOp::Replace, Maxwell::StencilOp::Incr, | ||
| 305 | Maxwell::StencilOp::Decr, Maxwell::StencilOp::Invert, | ||
| 306 | Maxwell::StencilOp::IncrWrap, Maxwell::StencilOp::DecrWrap}; | ||
| 307 | return LUT[packed]; | ||
| 308 | } | ||
| 309 | |||
| 274 | } // namespace Vulkan | 310 | } // namespace Vulkan |
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index d82a82f75..e30877e77 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h | |||
| @@ -24,27 +24,11 @@ inline constexpr bool IsHashable = std::has_unique_object_representations_v<T>&& | |||
| 24 | std::is_trivially_copyable_v<T>&& std::is_trivially_constructible_v<T>; | 24 | std::is_trivially_copyable_v<T>&& std::is_trivially_constructible_v<T>; |
| 25 | 25 | ||
| 26 | struct FixedPipelineState { | 26 | struct FixedPipelineState { |
| 27 | struct StencilFace { | 27 | static u32 PackComparisonOp(Maxwell::ComparisonOp op) noexcept; |
| 28 | constexpr StencilFace(Maxwell::StencilOp action_stencil_fail, | 28 | static Maxwell::ComparisonOp UnpackComparisonOp(u32 packed) noexcept; |
| 29 | Maxwell::StencilOp action_depth_fail, | ||
| 30 | Maxwell::StencilOp action_depth_pass, Maxwell::ComparisonOp test_func) | ||
| 31 | : action_stencil_fail{action_stencil_fail}, action_depth_fail{action_depth_fail}, | ||
| 32 | action_depth_pass{action_depth_pass}, test_func{test_func} {} | ||
| 33 | StencilFace() = default; | ||
| 34 | |||
| 35 | Maxwell::StencilOp action_stencil_fail; | ||
| 36 | Maxwell::StencilOp action_depth_fail; | ||
| 37 | Maxwell::StencilOp action_depth_pass; | ||
| 38 | Maxwell::ComparisonOp test_func; | ||
| 39 | 29 | ||
| 40 | std::size_t Hash() const noexcept; | 30 | static u32 PackStencilOp(Maxwell::StencilOp op) noexcept; |
| 41 | 31 | static Maxwell::StencilOp UnpackStencilOp(u32 packed) noexcept; | |
| 42 | bool operator==(const StencilFace& rhs) const noexcept; | ||
| 43 | |||
| 44 | bool operator!=(const StencilFace& rhs) const noexcept { | ||
| 45 | return !operator==(rhs); | ||
| 46 | } | ||
| 47 | }; | ||
| 48 | 32 | ||
| 49 | struct BlendingAttachment { | 33 | struct BlendingAttachment { |
| 50 | constexpr BlendingAttachment(bool enable, Maxwell::Blend::Equation rgb_equation, | 34 | constexpr BlendingAttachment(bool enable, Maxwell::Blend::Equation rgb_equation, |
| @@ -202,23 +186,42 @@ struct FixedPipelineState { | |||
| 202 | }; | 186 | }; |
| 203 | 187 | ||
| 204 | struct DepthStencil { | 188 | struct DepthStencil { |
| 205 | constexpr DepthStencil(bool depth_test_enable, bool depth_write_enable, | 189 | template <std::size_t Position> |
| 206 | bool depth_bounds_enable, bool stencil_enable, | 190 | union StencilFace { |
| 207 | Maxwell::ComparisonOp depth_test_function, StencilFace front_stencil, | 191 | BitField<Position + 0, 3, u32> action_stencil_fail; |
| 208 | StencilFace back_stencil) | 192 | BitField<Position + 3, 3, u32> action_depth_fail; |
| 209 | : depth_test_enable{depth_test_enable}, depth_write_enable{depth_write_enable}, | 193 | BitField<Position + 6, 3, u32> action_depth_pass; |
| 210 | depth_bounds_enable{depth_bounds_enable}, stencil_enable{stencil_enable}, | 194 | BitField<Position + 9, 3, u32> test_func; |
| 211 | depth_test_function{depth_test_function}, front_stencil{front_stencil}, | 195 | |
| 212 | back_stencil{back_stencil} {} | 196 | Maxwell::StencilOp ActionStencilFail() const noexcept { |
| 213 | DepthStencil() = default; | 197 | return UnpackStencilOp(action_stencil_fail); |
| 214 | 198 | } | |
| 215 | bool depth_test_enable; | 199 | |
| 216 | bool depth_write_enable; | 200 | Maxwell::StencilOp ActionDepthFail() const noexcept { |
| 217 | bool depth_bounds_enable; | 201 | return UnpackStencilOp(action_depth_fail); |
| 218 | bool stencil_enable; | 202 | } |
| 219 | Maxwell::ComparisonOp depth_test_function; | 203 | |
| 220 | StencilFace front_stencil; | 204 | Maxwell::StencilOp ActionDepthPass() const noexcept { |
| 221 | StencilFace back_stencil; | 205 | return UnpackStencilOp(action_depth_pass); |
| 206 | } | ||
| 207 | |||
| 208 | Maxwell::ComparisonOp TestFunc() const noexcept { | ||
| 209 | return UnpackComparisonOp(test_func); | ||
| 210 | } | ||
| 211 | }; | ||
| 212 | |||
| 213 | union { | ||
| 214 | u32 raw; | ||
| 215 | StencilFace<0> front; | ||
| 216 | StencilFace<12> back; | ||
| 217 | BitField<24, 1, u32> depth_test_enable; | ||
| 218 | BitField<25, 1, u32> depth_write_enable; | ||
| 219 | BitField<26, 1, u32> depth_bounds_enable; | ||
| 220 | BitField<27, 1, u32> stencil_enable; | ||
| 221 | BitField<28, 3, u32> depth_test_func; | ||
| 222 | }; | ||
| 223 | |||
| 224 | void Fill(const Maxwell& regs) noexcept; | ||
| 222 | 225 | ||
| 223 | std::size_t Hash() const noexcept; | 226 | std::size_t Hash() const noexcept; |
| 224 | 227 | ||
| @@ -227,7 +230,12 @@ struct FixedPipelineState { | |||
| 227 | bool operator!=(const DepthStencil& rhs) const noexcept { | 230 | bool operator!=(const DepthStencil& rhs) const noexcept { |
| 228 | return !operator==(rhs); | 231 | return !operator==(rhs); |
| 229 | } | 232 | } |
| 233 | |||
| 234 | Maxwell::ComparisonOp DepthTestFunc() const noexcept { | ||
| 235 | return UnpackComparisonOp(depth_test_func); | ||
| 236 | } | ||
| 230 | }; | 237 | }; |
| 238 | static_assert(IsHashable<DepthStencil>); | ||
| 231 | 239 | ||
| 232 | struct ColorBlending { | 240 | struct ColorBlending { |
| 233 | constexpr ColorBlending( | 241 | constexpr ColorBlending( |
| @@ -248,6 +256,13 @@ struct FixedPipelineState { | |||
| 248 | } | 256 | } |
| 249 | }; | 257 | }; |
| 250 | 258 | ||
| 259 | VertexInput vertex_input; | ||
| 260 | InputAssembly input_assembly; | ||
| 261 | Tessellation tessellation; | ||
| 262 | Rasterizer rasterizer; | ||
| 263 | DepthStencil depth_stencil; | ||
| 264 | ColorBlending color_blending; | ||
| 265 | |||
| 251 | std::size_t Hash() const noexcept; | 266 | std::size_t Hash() const noexcept; |
| 252 | 267 | ||
| 253 | bool operator==(const FixedPipelineState& rhs) const noexcept; | 268 | bool operator==(const FixedPipelineState& rhs) const noexcept; |
| @@ -255,15 +270,7 @@ struct FixedPipelineState { | |||
| 255 | bool operator!=(const FixedPipelineState& rhs) const noexcept { | 270 | bool operator!=(const FixedPipelineState& rhs) const noexcept { |
| 256 | return !operator==(rhs); | 271 | return !operator==(rhs); |
| 257 | } | 272 | } |
| 258 | |||
| 259 | VertexInput vertex_input; | ||
| 260 | InputAssembly input_assembly; | ||
| 261 | Tessellation tessellation; | ||
| 262 | Rasterizer rasterizer; | ||
| 263 | DepthStencil depth_stencil; | ||
| 264 | ColorBlending color_blending; | ||
| 265 | }; | 273 | }; |
| 266 | static_assert(std::is_trivially_copyable_v<FixedPipelineState::StencilFace>); | ||
| 267 | static_assert(std::is_trivially_copyable_v<FixedPipelineState::BlendingAttachment>); | 274 | static_assert(std::is_trivially_copyable_v<FixedPipelineState::BlendingAttachment>); |
| 268 | static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexInput>); | 275 | static_assert(std::is_trivially_copyable_v<FixedPipelineState::VertexInput>); |
| 269 | static_assert(std::is_trivially_copyable_v<FixedPipelineState::InputAssembly>); | 276 | static_assert(std::is_trivially_copyable_v<FixedPipelineState::InputAssembly>); |
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 718feafbd..0dd3ea5bc 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | |||
| @@ -26,12 +26,13 @@ MICROPROFILE_DECLARE(Vulkan_PipelineCache); | |||
| 26 | 26 | ||
| 27 | namespace { | 27 | namespace { |
| 28 | 28 | ||
| 29 | VkStencilOpState GetStencilFaceState(const FixedPipelineState::StencilFace& face) { | 29 | template <class StencilFace> |
| 30 | VkStencilOpState GetStencilFaceState(const StencilFace& face) { | ||
| 30 | VkStencilOpState state; | 31 | VkStencilOpState state; |
| 31 | state.failOp = MaxwellToVK::StencilOp(face.action_stencil_fail); | 32 | state.failOp = MaxwellToVK::StencilOp(face.ActionStencilFail()); |
| 32 | state.passOp = MaxwellToVK::StencilOp(face.action_depth_pass); | 33 | state.passOp = MaxwellToVK::StencilOp(face.ActionDepthPass()); |
| 33 | state.depthFailOp = MaxwellToVK::StencilOp(face.action_depth_fail); | 34 | state.depthFailOp = MaxwellToVK::StencilOp(face.ActionDepthFail()); |
| 34 | state.compareOp = MaxwellToVK::ComparisonOp(face.test_func); | 35 | state.compareOp = MaxwellToVK::ComparisonOp(face.TestFunc()); |
| 35 | state.compareMask = 0; | 36 | state.compareMask = 0; |
| 36 | state.writeMask = 0; | 37 | state.writeMask = 0; |
| 37 | state.reference = 0; | 38 | state.reference = 0; |
| @@ -277,13 +278,12 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa | |||
| 277 | depth_stencil_ci.flags = 0; | 278 | depth_stencil_ci.flags = 0; |
| 278 | depth_stencil_ci.depthTestEnable = ds.depth_test_enable; | 279 | depth_stencil_ci.depthTestEnable = ds.depth_test_enable; |
| 279 | depth_stencil_ci.depthWriteEnable = ds.depth_write_enable; | 280 | depth_stencil_ci.depthWriteEnable = ds.depth_write_enable; |
| 280 | depth_stencil_ci.depthCompareOp = ds.depth_test_enable | 281 | depth_stencil_ci.depthCompareOp = |
| 281 | ? MaxwellToVK::ComparisonOp(ds.depth_test_function) | 282 | ds.depth_test_enable ? MaxwellToVK::ComparisonOp(ds.DepthTestFunc()) : VK_COMPARE_OP_ALWAYS; |
| 282 | : VK_COMPARE_OP_ALWAYS; | ||
| 283 | depth_stencil_ci.depthBoundsTestEnable = ds.depth_bounds_enable; | 283 | depth_stencil_ci.depthBoundsTestEnable = ds.depth_bounds_enable; |
| 284 | depth_stencil_ci.stencilTestEnable = ds.stencil_enable; | 284 | depth_stencil_ci.stencilTestEnable = ds.stencil_enable; |
| 285 | depth_stencil_ci.front = GetStencilFaceState(ds.front_stencil); | 285 | depth_stencil_ci.front = GetStencilFaceState(ds.front); |
| 286 | depth_stencil_ci.back = GetStencilFaceState(ds.back_stencil); | 286 | depth_stencil_ci.back = GetStencilFaceState(ds.back); |
| 287 | depth_stencil_ci.minDepthBounds = 0.0f; | 287 | depth_stencil_ci.minDepthBounds = 0.0f; |
| 288 | depth_stencil_ci.maxDepthBounds = 0.0f; | 288 | depth_stencil_ci.maxDepthBounds = 0.0f; |
| 289 | 289 | ||