mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Merge remote-tracking branch 'vitor-k/holy_clipping_batman' into canary2798
This commit is contained in:
		
						commit
						aed2b73763
					
				
					 5 changed files with 115 additions and 22 deletions
				
			
		|  | @ -14,6 +14,7 @@ add_executable(tests | |||
|     audio_core/audio_fixures.h | ||||
|     audio_core/decoder_tests.cpp | ||||
|     video_core/shader/shader_jit_compiler.cpp | ||||
|     video_core/pica_float.cpp | ||||
|     audio_core/merryhime_3ds_audio/merry_audio/merry_audio.cpp | ||||
|     audio_core/merryhime_3ds_audio/merry_audio/merry_audio.h | ||||
|     audio_core/merryhime_3ds_audio/merry_audio/service_fixture.cpp | ||||
|  |  | |||
							
								
								
									
										30
									
								
								src/tests/video_core/pica_float.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/tests/video_core/pica_float.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| // Copyright 2024 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <cmath> | ||||
| #include <catch2/catch_approx.hpp> | ||||
| #include <catch2/catch_test_macros.hpp> | ||||
| #include "video_core/pica_types.h" | ||||
| 
 | ||||
| using Pica::f24; | ||||
| 
 | ||||
| TEST_CASE("Infinities", "[video_core][pica_float]") { | ||||
|     REQUIRE(std::isinf(f24::FromFloat32(INFINITY).ToFloat32())); | ||||
|     REQUIRE(std::isinf(f24::FromFloat32(1.e20f).ToFloat32())); | ||||
|     REQUIRE(std::isinf(f24::FromFloat32(-1.e20f).ToFloat32())); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("Subnormals", "[video_core][pica_float]") { | ||||
|     REQUIRE(f24::FromFloat32(1e-20f).ToFloat32() == 0.f); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("NaN", "[video_core][pica_float]") { | ||||
|     const auto inf = f24::FromFloat32(INFINITY); | ||||
|     const auto nan = f24::FromFloat32(NAN); | ||||
| 
 | ||||
|     REQUIRE(std::isnan(nan.ToFloat32())); | ||||
|     REQUIRE(std::isnan((nan * f24::Zero()).ToFloat32())); | ||||
|     REQUIRE(std::isnan((inf - inf).ToFloat32())); | ||||
|     REQUIRE((inf * f24::Zero()).ToFloat32() == 0.f); | ||||
| } | ||||
|  | @ -260,7 +260,7 @@ TEST_CASE("LG2", "[video_core][shader][shader_jit]") { | |||
|     REQUIRE(std::isinf(shader.Run(0.f).x)); | ||||
|     REQUIRE(shader.Run(4.f).x == Catch::Approx(2.f)); | ||||
|     REQUIRE(shader.Run(64.f).x == Catch::Approx(6.f)); | ||||
|     REQUIRE(shader.Run(1.e24f).x == Catch::Approx(79.7262742773f)); | ||||
|     // REQUIRE(std::isinf(shader.Run(INFINITY).x));
 | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("EX2", "[video_core][shader][shader_jit]") { | ||||
|  | @ -277,8 +277,9 @@ TEST_CASE("EX2", "[video_core][shader][shader_jit]") { | |||
|     REQUIRE(shader.Run(0.f).x == Catch::Approx(1.f)); | ||||
|     REQUIRE(shader.Run(2.f).x == Catch::Approx(4.f)); | ||||
|     REQUIRE(shader.Run(6.f).x == Catch::Approx(64.f)); | ||||
|     REQUIRE(shader.Run(79.7262742773f).x == Catch::Approx(1.e24f)); | ||||
|     REQUIRE(std::isinf(shader.Run(800.f).x)); | ||||
|     // If we respect f24 precision, 2^79 = inf, as 79 > 63
 | ||||
|     // REQUIRE(std::isinf(shader.Run(79.7262742773f).x));
 | ||||
| } | ||||
| 
 | ||||
| TEST_CASE("MUL", "[video_core][shader][shader_jit]") { | ||||
|  | @ -469,7 +470,7 @@ TEST_CASE("Uniform Read", "[video_core][shader][shader_jit]") { | |||
|         const float color = (i * 2.0f) / 255.0f; | ||||
|         const auto color_f24 = Pica::f24::FromFloat32(color); | ||||
|         shader.shader_setup->uniforms.f[i] = {color_f24, color_f24, color_f24, Pica::f24::One()}; | ||||
|         f_uniforms[i] = {color, color, color, 1.0f}; | ||||
|         f_uniforms[i] = {color_f24.ToFloat32(), color_f24.ToFloat32(), color_f24.ToFloat32(), 1.0f}; | ||||
|     } | ||||
| 
 | ||||
|     for (u32 i = 0; i < 96; ++i) { | ||||
|  | @ -506,7 +507,8 @@ TEST_CASE("Address Register Offset", "[video_core][shader][shader_jit]") { | |||
|             const auto color_f24 = Pica::f24::FromFloat32(color); | ||||
|             shader.shader_setup->uniforms.f[i] = {color_f24, color_f24, color_f24, | ||||
|                                                   Pica::f24::One()}; | ||||
|             f_uniforms[i] = {color, color, color, 1.f}; | ||||
|             f_uniforms[i] = {color_f24.ToFloat32(), color_f24.ToFloat32(), color_f24.ToFloat32(), | ||||
|                              1.f}; | ||||
|         } else if (i >= 0x60 && i < 0x64) { | ||||
|             const u8 color = static_cast<u8>((i - 0x60) * 0x10); | ||||
|             shader.shader_setup->uniforms.i[i - 0x60] = {color, color, color, 255}; | ||||
|  |  | |||
|  | @ -4,8 +4,10 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <bit> | ||||
| #include <cmath> | ||||
| #include <cstring> | ||||
| #include <limits> | ||||
| #include <boost/serialization/access.hpp> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
|  | @ -28,6 +30,41 @@ public: | |||
|     static constexpr Float<M, E> FromFloat32(float val) { | ||||
|         Float<M, E> ret; | ||||
|         ret.value = val; | ||||
|         return Trunc(ret); | ||||
|     } | ||||
| 
 | ||||
|     static constexpr Float<M, E> MinNormal() { | ||||
|         Float<M, E> ret; | ||||
|         // Mininum normal value = 1.0 / (1 << ((1 << (E - 1)) - 2));
 | ||||
|         if constexpr (E == 5) { | ||||
|             ret.value = 0x1.p-14; | ||||
|         } else { | ||||
|             // E == 7
 | ||||
|             ret.value = (0x1.p-62); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     // these values are approximate, rounded up
 | ||||
|     static constexpr Float<M, E> Max() { | ||||
|         Float<M, E> ret; | ||||
|         if constexpr (E == 5) { | ||||
|             ret.value = 0x1.p16; | ||||
|         } else { | ||||
|             // E == 7
 | ||||
|             ret.value = 0x1.p64; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     // before C++23 std::isnormal and std::abs aren't considered constexpr so this function can't be
 | ||||
|     // used as constexpr until the compilers support that.
 | ||||
|     static constexpr Float<M, E> Trunc(const Float<M, E>& val) { | ||||
|         Float<M, E> ret = val.Flushed().InfChecked(); | ||||
|         if (std::isnormal(val.ToFloat32())) { | ||||
|             u32 hex = std::bit_cast<u32>(ret.ToFloat32()) & (0xffffffff ^ ((1 << (23 - M)) - 1)); | ||||
|             ret.value = std::bit_cast<float>(hex); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|  | @ -50,17 +87,21 @@ public: | |||
|             hex = sign; | ||||
|         } | ||||
| 
 | ||||
|         std::memcpy(&res.value, &hex, sizeof(float)); | ||||
|         res.value = std::bit_cast<float>(hex); | ||||
| 
 | ||||
|         return res; | ||||
|     } | ||||
| 
 | ||||
|     static constexpr Float<M, E> Zero() { | ||||
|         return FromFloat32(0.f); | ||||
|         Float<M, E> ret; | ||||
|         ret.value = 0.f; | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     static constexpr Float<M, E> One() { | ||||
|         return FromFloat32(1.f); | ||||
|         Float<M, E> ret; | ||||
|         ret.value = 1.f; | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     // Not recommended for anything but logging
 | ||||
|  | @ -68,6 +109,24 @@ public: | |||
|         return value; | ||||
|     } | ||||
| 
 | ||||
|     constexpr Float<M, E> Flushed() const { | ||||
|         Float<M, E> ret; | ||||
|         ret.value = value; | ||||
|         if (std::abs(value) < MinNormal().ToFloat32()) { | ||||
|             ret.value = 0; | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     constexpr Float<M, E> InfChecked() const { | ||||
|         Float<M, E> ret; | ||||
|         ret.value = value; | ||||
|         if (std::abs(value) > Max().ToFloat32()) { | ||||
|             ret.value = value * std::numeric_limits<float>::infinity(); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     constexpr Float<M, E> operator*(const Float<M, E>& flt) const { | ||||
|         float result = value * flt.ToFloat32(); | ||||
|         // PICA gives 0 instead of NaN when multiplying by inf
 | ||||
|  | @ -95,22 +154,24 @@ public: | |||
|     } | ||||
| 
 | ||||
|     constexpr Float<M, E>& operator/=(const Float<M, E>& flt) { | ||||
|         value /= flt.ToFloat32(); | ||||
|         value = operator/(flt).value; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     constexpr Float<M, E>& operator+=(const Float<M, E>& flt) { | ||||
|         value += flt.ToFloat32(); | ||||
|         value = operator+(flt).value; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     constexpr Float<M, E>& operator-=(const Float<M, E>& flt) { | ||||
|         value -= flt.ToFloat32(); | ||||
|         value = operator-(flt).value; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     constexpr Float<M, E> operator-() const { | ||||
|         return Float<M, E>::FromFloat32(-ToFloat32()); | ||||
|         Float<M, E> ret; | ||||
|         ret.value = -value; | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     constexpr bool operator<(const Float<M, E>& flt) const { | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ using Pica::Texture::TextureInfo; | |||
| // negative/positive z values when computing with f32 precision,
 | ||||
| // causing some vertices to get erroneously clipped. To workaround this problem,
 | ||||
| // we can use a very small epsilon value for clip plane comparison.
 | ||||
| constexpr f32 EPSILON_Z = 0.00000001f; | ||||
| constexpr f32 EPSILON_Z = 0.f; | ||||
| 
 | ||||
| struct Vertex : Pica::OutputVertex { | ||||
|     Vertex(const OutputVertex& v) : OutputVertex(v) {} | ||||
|  | @ -125,9 +125,8 @@ void RasterizerSoftware::AddTriangle(const Pica::OutputVertex& v0, const Pica::O | |||
|     auto* input_list = &buffer_b; | ||||
| 
 | ||||
|     // NOTE: We clip against a w=epsilon plane to guarantee that the output has a positive w value.
 | ||||
|     // TODO: Not sure if this is a valid approach. Also should probably instead use the smallest
 | ||||
|     //       epsilon possible within f24 accuracy.
 | ||||
|     static constexpr f24 EPSILON = f24::FromFloat32(0.00001f); | ||||
|     // TODO: Not sure if this is a valid approach.
 | ||||
|     static constexpr f24 EPSILON = f24::MinNormal(); | ||||
|     static constexpr f24 f0 = f24::Zero(); | ||||
|     static constexpr f24 f1 = f24::One(); | ||||
|     static constexpr std::array<ClippingEdge, 7> clipping_edges = {{ | ||||
|  | @ -287,11 +286,11 @@ void RasterizerSoftware::ProcessTriangle(const Vertex& v0, const Vertex& v1, con | |||
|     max_y = ((max_y + Fix12P4::FracMask()) & Fix12P4::IntMask()); | ||||
| 
 | ||||
|     const int bias0 = | ||||
|         IsRightSideOrFlatBottomEdge(vtxpos[0].xy(), vtxpos[1].xy(), vtxpos[2].xy()) ? -1 : 0; | ||||
|         IsRightSideOrFlatBottomEdge(vtxpos[0].xy(), vtxpos[1].xy(), vtxpos[2].xy()) ? 1 : 0; | ||||
|     const int bias1 = | ||||
|         IsRightSideOrFlatBottomEdge(vtxpos[1].xy(), vtxpos[2].xy(), vtxpos[0].xy()) ? -1 : 0; | ||||
|         IsRightSideOrFlatBottomEdge(vtxpos[1].xy(), vtxpos[2].xy(), vtxpos[0].xy()) ? 1 : 0; | ||||
|     const int bias2 = | ||||
|         IsRightSideOrFlatBottomEdge(vtxpos[2].xy(), vtxpos[0].xy(), vtxpos[1].xy()) ? -1 : 0; | ||||
|         IsRightSideOrFlatBottomEdge(vtxpos[2].xy(), vtxpos[0].xy(), vtxpos[1].xy()) ? 1 : 0; | ||||
| 
 | ||||
|     const auto w_inverse = Common::MakeVec(v0.pos.w, v1.pos.w, v2.pos.w); | ||||
| 
 | ||||
|  | @ -314,13 +313,13 @@ void RasterizerSoftware::ProcessTriangle(const Vertex& v0, const Vertex& v1, con | |||
|                 } | ||||
| 
 | ||||
|                 // Calculate the barycentric coordinates w0, w1 and w2
 | ||||
|                 const s32 w0 = bias0 + SignedArea(vtxpos[1].xy(), vtxpos[2].xy(), {x, y}); | ||||
|                 const s32 w1 = bias1 + SignedArea(vtxpos[2].xy(), vtxpos[0].xy(), {x, y}); | ||||
|                 const s32 w2 = bias2 + SignedArea(vtxpos[0].xy(), vtxpos[1].xy(), {x, y}); | ||||
|                 const s32 w0 = SignedArea(vtxpos[1].xy(), vtxpos[2].xy(), {x, y}); | ||||
|                 const s32 w1 = SignedArea(vtxpos[2].xy(), vtxpos[0].xy(), {x, y}); | ||||
|                 const s32 w2 = SignedArea(vtxpos[0].xy(), vtxpos[1].xy(), {x, y}); | ||||
|                 const s32 wsum = w0 + w1 + w2; | ||||
| 
 | ||||
|                 // If current pixel is not covered by the current primitive
 | ||||
|                 if (w0 < 0 || w1 < 0 || w2 < 0) { | ||||
|                 if (w0 < bias0 || w1 < bias1 || w2 < bias2) { | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue